| 1 |  |  package liquibase.parser.core.xml; | 
  | 2 |  |   | 
  | 3 |  |  import java.io.File; | 
  | 4 |  |  import java.io.IOException; | 
  | 5 |  |  import java.io.InputStream; | 
  | 6 |  |  import java.lang.reflect.InvocationTargetException; | 
  | 7 |  |  import java.lang.reflect.Method; | 
  | 8 |  |  import java.net.URL; | 
  | 9 |  |  import java.net.URLDecoder; | 
  | 10 |  |  import java.util.Arrays; | 
  | 11 |  |  import java.util.Comparator; | 
  | 12 |  |  import java.util.Enumeration; | 
  | 13 |  |  import java.util.HashSet; | 
  | 14 |  |  import java.util.List; | 
  | 15 |  |  import java.util.Map; | 
  | 16 |  |  import java.util.Properties; | 
  | 17 |  |  import java.util.Set; | 
  | 18 |  |  import java.util.SortedSet; | 
  | 19 |  |  import java.util.Stack; | 
  | 20 |  |  import java.util.TreeSet; | 
  | 21 |  |  import java.util.jar.JarEntry; | 
  | 22 |  |  import java.util.jar.JarFile; | 
  | 23 |  |  import java.util.regex.Matcher; | 
  | 24 |  |  import java.util.regex.Pattern; | 
  | 25 |  |   | 
  | 26 |  |  import liquibase.change.Change; | 
  | 27 |  |  import liquibase.change.ChangeFactory; | 
  | 28 |  |  import liquibase.change.ChangeWithColumns; | 
  | 29 |  |  import liquibase.change.ColumnConfig; | 
  | 30 |  |  import liquibase.change.ConstraintsConfig; | 
  | 31 |  |  import liquibase.change.core.CreateProcedureChange; | 
  | 32 |  |  import liquibase.change.core.CreateViewChange; | 
  | 33 |  |  import liquibase.change.core.DeleteDataChange; | 
  | 34 |  |  import liquibase.change.core.ExecuteShellCommandChange; | 
  | 35 |  |  import liquibase.change.core.InsertDataChange; | 
  | 36 |  |  import liquibase.change.core.LoadDataChange; | 
  | 37 |  |  import liquibase.change.core.LoadDataColumnConfig; | 
  | 38 |  |  import liquibase.change.core.RawSQLChange; | 
  | 39 |  |  import liquibase.change.core.StopChange; | 
  | 40 |  |  import liquibase.change.core.UpdateDataChange; | 
  | 41 |  |  import liquibase.change.custom.CustomChangeWrapper; | 
  | 42 |  |  import liquibase.changelog.ChangeLogParameters; | 
  | 43 |  |  import liquibase.changelog.ChangeSet; | 
  | 44 |  |  import liquibase.changelog.DatabaseChangeLog; | 
  | 45 |  |  import liquibase.exception.CustomChangeException; | 
  | 46 |  |  import liquibase.exception.LiquibaseException; | 
  | 47 |  |  import liquibase.exception.MigrationFailedException; | 
  | 48 |  |  import liquibase.logging.LogFactory; | 
  | 49 |  |  import liquibase.logging.Logger; | 
  | 50 |  |  import liquibase.parser.ChangeLogParserFactory; | 
  | 51 |  |  import liquibase.precondition.CustomPreconditionWrapper; | 
  | 52 |  |  import liquibase.precondition.Precondition; | 
  | 53 |  |  import liquibase.precondition.PreconditionFactory; | 
  | 54 |  |  import liquibase.precondition.PreconditionLogic; | 
  | 55 |  |  import liquibase.precondition.core.PreconditionContainer; | 
  | 56 |  |  import liquibase.precondition.core.SqlPrecondition; | 
  | 57 |  |  import liquibase.resource.ResourceAccessor; | 
  | 58 |  |  import liquibase.sql.visitor.SqlVisitor; | 
  | 59 |  |  import liquibase.sql.visitor.SqlVisitorFactory; | 
  | 60 |  |  import liquibase.util.ObjectUtil; | 
  | 61 |  |  import liquibase.util.StringUtils; | 
  | 62 |  |  import liquibase.util.file.FilenameUtils; | 
  | 63 |  |   | 
  | 64 |  |  import org.xml.sax.Attributes; | 
  | 65 |  |  import org.xml.sax.SAXException; | 
  | 66 |  |  import org.xml.sax.helpers.DefaultHandler; | 
  | 67 |  |   | 
  | 68 | 285 |  class XMLChangeLogSAXHandler extends DefaultHandler { | 
  | 69 |  |   | 
  | 70 |  |      private static final char LIQUIBASE_FILE_SEPARATOR = '/'; | 
  | 71 |  |   | 
  | 72 |  |      protected Logger log; | 
  | 73 |  |   | 
  | 74 |  |      private DatabaseChangeLog databaseChangeLog; | 
  | 75 |  |      private Change change; | 
  | 76 | 16 |      private Stack changeSubObjects = new Stack(); | 
  | 77 |  |      private StringBuffer text; | 
  | 78 |  |      private PreconditionContainer rootPrecondition; | 
  | 79 | 16 |      private Stack<PreconditionLogic> preconditionLogicStack = new Stack<PreconditionLogic>(); | 
  | 80 |  |      private ChangeSet changeSet; | 
  | 81 |  |      private String paramName; | 
  | 82 |  |      private ResourceAccessor resourceAccessor; | 
  | 83 |  |      private Precondition currentPrecondition; | 
  | 84 |  |   | 
  | 85 |  |      private ChangeLogParameters changeLogParameters; | 
  | 86 | 16 |      private boolean inRollback = false; | 
  | 87 |  |   | 
  | 88 | 16 |      private boolean inModifySql = false; | 
  | 89 |  |      private Set<String> modifySqlDbmsList; | 
  | 90 |  |      private Set<String> modifySqlContexts; | 
  | 91 | 16 |      private boolean modifySqlAppliedOnRollback = false; | 
  | 92 |  |   | 
  | 93 |  |      protected XMLChangeLogSAXHandler(String physicalChangeLogLocation, ResourceAccessor resourceAccessor, | 
  | 94 | 16 |              ChangeLogParameters changeLogParameters) { | 
  | 95 | 16 |          log = LogFactory.getLogger(); | 
  | 96 | 16 |          this.resourceAccessor = resourceAccessor; | 
  | 97 |  |   | 
  | 98 | 16 |          databaseChangeLog = new DatabaseChangeLog(); | 
  | 99 | 16 |          databaseChangeLog.setPhysicalFilePath(physicalChangeLogLocation); | 
  | 100 | 16 |          databaseChangeLog.setChangeLogParameters(changeLogParameters); | 
  | 101 |  |   | 
  | 102 | 16 |          this.changeLogParameters = changeLogParameters; | 
  | 103 | 16 |      } | 
  | 104 |  |   | 
  | 105 |  |      public DatabaseChangeLog getDatabaseChangeLog() { | 
  | 106 | 15 |          return databaseChangeLog; | 
  | 107 |  |      } | 
  | 108 |  |   | 
  | 109 |  |      @Override | 
  | 110 |  |      public void startElement(String uri, String localName, String qName, Attributes baseAttributes) throws SAXException { | 
  | 111 | 163 |          Attributes atts = new ExpandingAttributes(baseAttributes); | 
  | 112 |  |          try { | 
  | 113 | 163 |              if ("comment".equals(qName)) { | 
  | 114 | 7 |                  text = new StringBuffer(); | 
  | 115 | 156 |              } else if ("validCheckSum".equals(qName)) { | 
  | 116 | 0 |                  text = new StringBuffer(); | 
  | 117 | 156 |              } else if ("databaseChangeLog".equals(qName)) { | 
  | 118 | 16 |                  String schemaLocation = atts.getValue("xsi:schemaLocation"); | 
  | 119 | 16 |                  if (schemaLocation != null) { | 
  | 120 | 16 |                      Matcher matcher = Pattern.compile(".*dbchangelog-(\\d+\\.\\d+).xsd").matcher(schemaLocation); | 
  | 121 | 16 |                      if (matcher.matches()) { | 
  | 122 | 16 |                          String version = matcher.group(1); | 
  | 123 | 16 |                          if (!version.equals(XMLChangeLogSAXParser.getSchemaVersion())) { | 
  | 124 | 16 |                              log.info(databaseChangeLog.getPhysicalFilePath() + " is using schema version " + version | 
  | 125 |  |                                      + " rather than version " + XMLChangeLogSAXParser.getSchemaVersion()); | 
  | 126 |  |                          } | 
  | 127 |  |                      } | 
  | 128 |  |                  } | 
  | 129 | 16 |                  databaseChangeLog.setLogicalFilePath(atts.getValue("logicalFilePath")); | 
  | 130 | 16 |              } else if ("include".equals(qName)) { | 
  | 131 | 6 |                  String fileName = atts.getValue("file"); | 
  | 132 | 6 |                  fileName = fileName.replace('\\', '/'); | 
  | 133 | 6 |                  boolean isRelativeToChangelogFile = Boolean.parseBoolean(atts.getValue("relativeToChangelogFile")); | 
  | 134 | 6 |                  handleIncludedChangeLog(fileName, isRelativeToChangelogFile, databaseChangeLog.getPhysicalFilePath()); | 
  | 135 | 6 |              } else if ("includeAll".equals(qName)) { | 
  | 136 | 0 |                  String pathName = atts.getValue("path"); | 
  | 137 | 0 |                  pathName = pathName.replace('\\', '/'); | 
  | 138 |  |   | 
  | 139 | 0 |                  if (!(pathName.endsWith("/"))) { | 
  | 140 | 0 |                      pathName = pathName + '/'; | 
  | 141 |  |                  } | 
  | 142 | 0 |                  log.debug("includeAll for " + pathName); | 
  | 143 | 0 |                  log.debug("Using file opener for includeAll: " + resourceAccessor.toString()); | 
  | 144 | 0 |                  boolean isRelativeToChangelogFile = Boolean.parseBoolean(atts.getValue("relativeToChangelogFile")); | 
  | 145 |  |   | 
  | 146 | 0 |                  if (isRelativeToChangelogFile) { | 
  | 147 | 0 |                      File changeLogFile = new File(databaseChangeLog.getPhysicalFilePath()); | 
  | 148 | 0 |                      File resourceBase = new File(changeLogFile.getParent(), pathName); | 
  | 149 | 0 |                      if (!resourceBase.exists()) { | 
  | 150 | 0 |                          throw new SAXException("Resource directory for includeAll does not exist [" | 
  | 151 |  |                                  + resourceBase.getPath() + "]"); | 
  | 152 |  |                      } | 
  | 153 | 0 |                      pathName = resourceBase.getPath() + '/'; | 
  | 154 | 0 |                      pathName = pathName.replace('\\', '/'); | 
  | 155 |  |                  } | 
  | 156 |  |   | 
  | 157 | 0 |                  Enumeration<URL> resourcesEnum = resourceAccessor.getResources(pathName); | 
  | 158 | 0 |                  SortedSet<URL> resources = new TreeSet<URL>(new Comparator<URL>() { | 
  | 159 |  |                      @Override | 
  | 160 |  |                      public int compare(URL o1, URL o2) { | 
  | 161 | 0 |                          return o1.toString().compareTo(o2.toString()); | 
  | 162 |  |                      } | 
  | 163 |  |                  }); | 
  | 164 | 0 |                  while (resourcesEnum.hasMoreElements()) { | 
  | 165 | 0 |                      resources.add(resourcesEnum.nextElement()); | 
  | 166 |  |                  } | 
  | 167 |  |   | 
  | 168 | 0 |                  boolean foundResource = false; | 
  | 169 |  |   | 
  | 170 | 0 |                  Set<String> seenPaths = new HashSet<String>(); | 
  | 171 | 0 |                  for (URL fileUrl : resources) { | 
  | 172 | 0 |                      if (!fileUrl.toExternalForm().startsWith("file:")) { | 
  | 173 | 0 |                          if (fileUrl.toExternalForm().startsWith("jar:file:") | 
  | 174 |  |                                  || fileUrl.toExternalForm().startsWith("wsjar:file:") | 
  | 175 |  |                                  || fileUrl.toExternalForm().startsWith("zip:")) { | 
  | 176 | 0 |                              File zipFileDir = extractZipFile(fileUrl); | 
  | 177 | 0 |                              fileUrl = new File(zipFileDir, pathName).toURL(); | 
  | 178 | 0 |                          } else { | 
  | 179 | 0 |                              log.debug(fileUrl.toExternalForm() + " is not a file path"); | 
  | 180 | 0 |                              continue; | 
  | 181 |  |                          } | 
  | 182 |  |                      } | 
  | 183 | 0 |                      File file = new File(fileUrl.toURI()); | 
  | 184 | 0 |                      log.debug("includeAll using path " + file.getCanonicalPath()); | 
  | 185 | 0 |                      if (!file.exists()) { | 
  | 186 | 0 |                          throw new SAXException("includeAll path " + pathName + " could not be found.  Tried in " | 
  | 187 |  |                                  + file.toString()); | 
  | 188 |  |                      } | 
  | 189 | 0 |                      if (file.isDirectory()) { | 
  | 190 | 0 |                          log.debug(file.getCanonicalPath() + " is a directory"); | 
  | 191 | 0 |                          for (File childFile : new TreeSet<File>(Arrays.asList(file.listFiles()))) { | 
  | 192 | 0 |                              String path = pathName + childFile.getName(); | 
  | 193 | 0 |                              if (!seenPaths.add(path)) { | 
  | 194 | 0 |                                  log.debug("already included " + path); | 
  | 195 | 0 |                                  continue; | 
  | 196 |  |                              } | 
  | 197 |  |   | 
  | 198 | 0 |                              if (handleIncludedChangeLog(path, false, databaseChangeLog.getPhysicalFilePath())) { | 
  | 199 | 0 |                                  foundResource = true; | 
  | 200 |  |                              } | 
  | 201 | 0 |                          } | 
  | 202 |  |                      } else { | 
  | 203 | 0 |                          String path = pathName + file.getName(); | 
  | 204 | 0 |                          if (!seenPaths.add(path)) { | 
  | 205 | 0 |                              log.debug("already included " + path); | 
  | 206 | 0 |                              continue; | 
  | 207 |  |                          } | 
  | 208 | 0 |                          if (handleIncludedChangeLog(path, false, databaseChangeLog.getPhysicalFilePath())) { | 
  | 209 | 0 |                              foundResource = true; | 
  | 210 |  |                          } | 
  | 211 |  |                      } | 
  | 212 | 0 |                  } | 
  | 213 |  |   | 
  | 214 | 0 |                  if (!foundResource) { | 
  | 215 | 0 |                      throw new SAXException("Could not find directory or directory was empty for includeAll '" | 
  | 216 |  |                              + pathName + "'"); | 
  | 217 |  |                  } | 
  | 218 | 0 |              } else if (changeSet == null && "changeSet".equals(qName)) { | 
  | 219 | 23 |                  boolean alwaysRun = false; | 
  | 220 | 23 |                  boolean runOnChange = false; | 
  | 221 | 23 |                  if ("true".equalsIgnoreCase(atts.getValue("runAlways"))) { | 
  | 222 | 1 |                      alwaysRun = true; | 
  | 223 |  |                  } | 
  | 224 | 23 |                  if ("true".equalsIgnoreCase(atts.getValue("runOnChange"))) { | 
  | 225 | 1 |                      runOnChange = true; | 
  | 226 |  |                  } | 
  | 227 | 23 |                  String filePath = atts.getValue("logicalFilePath"); | 
  | 228 | 23 |                  if (filePath == null || "".equals(filePath)) { | 
  | 229 | 23 |                      filePath = databaseChangeLog.getFilePath(); | 
  | 230 |  |                  } | 
  | 231 |  |   | 
  | 232 | 23 |                  changeSet = new ChangeSet(atts.getValue("id"), atts.getValue("author"), alwaysRun, runOnChange, | 
  | 233 |  |                          filePath, atts.getValue("context"), atts.getValue("dbms"), Boolean.valueOf(atts | 
  | 234 |  |                                  .getValue("runInTransaction"))); | 
  | 235 | 23 |                  if (StringUtils.trimToNull(atts.getValue("failOnError")) != null) { | 
  | 236 | 0 |                      changeSet.setFailOnError(Boolean.parseBoolean(atts.getValue("failOnError"))); | 
  | 237 |  |                  } | 
  | 238 | 23 |                  if (StringUtils.trimToNull(atts.getValue("onValidationFail")) != null) { | 
  | 239 | 0 |                      changeSet.setOnValidationFail(ChangeSet.ValidationFailOption.valueOf(atts | 
  | 240 |  |                              .getValue("onValidationFail"))); | 
  | 241 |  |                  } | 
  | 242 | 23 |              } else if (changeSet != null && "rollback".equals(qName)) { | 
  | 243 | 1 |                  text = new StringBuffer(); | 
  | 244 | 1 |                  String id = atts.getValue("changeSetId"); | 
  | 245 | 1 |                  if (id != null) { | 
  | 246 | 0 |                      String path = atts.getValue("changeSetPath"); | 
  | 247 | 0 |                      if (path == null) { | 
  | 248 | 0 |                          path = databaseChangeLog.getFilePath(); | 
  | 249 |  |                      } | 
  | 250 | 0 |                      String author = atts.getValue("changeSetAuthor"); | 
  | 251 | 0 |                      ChangeSet changeSet = databaseChangeLog.getChangeSet(path, author, id); | 
  | 252 | 0 |                      if (changeSet == null) { | 
  | 253 | 0 |                          throw new SAXException("Could not find changeSet to use for rollback: " + path + ":" + author | 
  | 254 |  |                                  + ":" + id); | 
  | 255 |  |                      } else { | 
  | 256 | 0 |                          for (Change change : changeSet.getChanges()) { | 
  | 257 | 0 |                              this.changeSet.addRollbackChange(change); | 
  | 258 |  |                          } | 
  | 259 |  |                      } | 
  | 260 |  |                  } | 
  | 261 | 1 |                  inRollback = true; | 
  | 262 | 1 |              } else if ("preConditions".equals(qName)) { | 
  | 263 | 1 |                  rootPrecondition = new PreconditionContainer(); | 
  | 264 | 1 |                  rootPrecondition.setOnFail(StringUtils.trimToNull(atts.getValue("onFail"))); | 
  | 265 | 1 |                  rootPrecondition.setOnError(StringUtils.trimToNull(atts.getValue("onError"))); | 
  | 266 | 1 |                  rootPrecondition.setOnFailMessage(StringUtils.trimToNull(atts.getValue("onFailMessage"))); | 
  | 267 | 1 |                  rootPrecondition.setOnErrorMessage(StringUtils.trimToNull(atts.getValue("onErrorMessage"))); | 
  | 268 | 1 |                  rootPrecondition.setOnSqlOutput(StringUtils.trimToNull(atts.getValue("onSqlOutput"))); | 
  | 269 | 1 |                  preconditionLogicStack.push(rootPrecondition); | 
  | 270 | 109 |              } else if (currentPrecondition != null && currentPrecondition instanceof CustomPreconditionWrapper | 
  | 271 |  |                      && qName.equals("param")) { | 
  | 272 | 0 |                  ((CustomPreconditionWrapper) currentPrecondition).setParam(atts.getValue("name"), | 
  | 273 |  |                          atts.getValue("value")); | 
  | 274 | 109 |              } else if (rootPrecondition != null) { | 
  | 275 | 4 |                  currentPrecondition = PreconditionFactory.getInstance().create(qName); | 
  | 276 |  |   | 
  | 277 | 7 |                  for (int i = 0; i < atts.getLength(); i++) { | 
  | 278 | 3 |                      String attributeName = atts.getQName(i); | 
  | 279 | 3 |                      String attributeValue = atts.getValue(i); | 
  | 280 | 3 |                      setProperty(currentPrecondition, attributeName, attributeValue); | 
  | 281 |  |                  } | 
  | 282 | 4 |                  preconditionLogicStack.peek().addNestedPrecondition(currentPrecondition); | 
  | 283 |  |   | 
  | 284 | 4 |                  if (currentPrecondition instanceof PreconditionLogic) { | 
  | 285 | 1 |                      preconditionLogicStack.push(((PreconditionLogic) currentPrecondition)); | 
  | 286 |  |                  } | 
  | 287 |  |   | 
  | 288 | 4 |                  if ("sqlCheck".equals(qName)) { | 
  | 289 | 0 |                      text = new StringBuffer(); | 
  | 290 |  |                  } | 
  | 291 | 105 |              } else if ("modifySql".equals(qName)) { | 
  | 292 | 0 |                  inModifySql = true; | 
  | 293 | 0 |                  if (StringUtils.trimToNull(atts.getValue("dbms")) != null) { | 
  | 294 | 0 |                      modifySqlDbmsList = new HashSet<String>(StringUtils.splitAndTrim(atts.getValue("dbms"), ",")); | 
  | 295 |  |                  } | 
  | 296 | 0 |                  if (StringUtils.trimToNull(atts.getValue("context")) != null) { | 
  | 297 | 0 |                      modifySqlContexts = new HashSet<String>(StringUtils.splitAndTrim(atts.getValue("context"), ",")); | 
  | 298 |  |                  } | 
  | 299 | 0 |                  if (StringUtils.trimToNull(atts.getValue("applyToRollback")) != null) { | 
  | 300 | 0 |                      modifySqlAppliedOnRollback = Boolean.valueOf(atts.getValue("applyToRollback")); | 
  | 301 |  |                  } | 
  | 302 | 105 |              } else if (inModifySql) { | 
  | 303 | 0 |                  SqlVisitor sqlVisitor = SqlVisitorFactory.getInstance().create(qName); | 
  | 304 | 0 |                  for (int i = 0; i < atts.getLength(); i++) { | 
  | 305 | 0 |                      String attributeName = atts.getQName(i); | 
  | 306 | 0 |                      String attributeValue = atts.getValue(i); | 
  | 307 | 0 |                      setProperty(sqlVisitor, attributeName, attributeValue); | 
  | 308 |  |                  } | 
  | 309 | 0 |                  sqlVisitor.setApplicableDbms(modifySqlDbmsList); | 
  | 310 | 0 |                  sqlVisitor.setApplyToRollback(modifySqlAppliedOnRollback); | 
  | 311 | 0 |                  sqlVisitor.setContexts(modifySqlContexts); | 
  | 312 |  |   | 
  | 313 | 0 |                  changeSet.addSqlVisitor(sqlVisitor); | 
  | 314 | 0 |              } else if (changeSet != null && change == null) { | 
  | 315 | 23 |                  change = ChangeFactory.getInstance().create(localName); | 
  | 316 | 23 |                  if (change == null) { | 
  | 317 | 0 |                      throw new SAXException("Unknown Liquibase extension: " + localName | 
  | 318 |  |                              + ".  Are you missing a jar from your classpath?"); | 
  | 319 |  |                  } | 
  | 320 | 23 |                  change.setChangeSet(changeSet); | 
  | 321 | 23 |                  text = new StringBuffer(); | 
  | 322 | 23 |                  if (change == null) { | 
  | 323 | 0 |                      throw new MigrationFailedException(changeSet, "Unknown change: " + localName); | 
  | 324 |  |                  } | 
  | 325 | 23 |                  change.setResourceAccessor(resourceAccessor); | 
  | 326 | 23 |                  if (change instanceof CustomChangeWrapper) { | 
  | 327 | 1 |                      ((CustomChangeWrapper) change).setClassLoader(resourceAccessor.toClassLoader()); | 
  | 328 |  |                  } | 
  | 329 | 46 |                  for (int i = 0; i < atts.getLength(); i++) { | 
  | 330 | 23 |                      String attributeName = atts.getLocalName(i); | 
  | 331 | 23 |                      String attributeValue = atts.getValue(i); | 
  | 332 | 23 |                      setProperty(change, attributeName, attributeValue); | 
  | 333 |  |                  } | 
  | 334 | 23 |                  change.init(); | 
  | 335 | 82 |              } else if (change != null && "column".equals(qName)) { | 
  | 336 |  |                  ColumnConfig column; | 
  | 337 | 51 |                  if (change instanceof LoadDataChange) { | 
  | 338 | 0 |                      column = new LoadDataColumnConfig(); | 
  | 339 |  |                  } else { | 
  | 340 | 51 |                      column = new ColumnConfig(); | 
  | 341 |  |                  } | 
  | 342 | 153 |                  for (int i = 0; i < atts.getLength(); i++) { | 
  | 343 | 102 |                      String attributeName = atts.getQName(i); | 
  | 344 | 102 |                      String attributeValue = atts.getValue(i); | 
  | 345 | 102 |                      setProperty(column, attributeName, attributeValue); | 
  | 346 |  |                  } | 
  | 347 | 51 |                  if (change instanceof ChangeWithColumns) { | 
  | 348 | 51 |                      ((ChangeWithColumns) change).addColumn(column); | 
  | 349 |  |                  } else { | 
  | 350 | 0 |                      throw new RuntimeException("Unexpected column tag for " + change.getClass().getName()); | 
  | 351 |  |                  } | 
  | 352 | 51 |              } else if (change != null && "constraints".equals(qName)) { | 
  | 353 | 29 |                  ConstraintsConfig constraints = new ConstraintsConfig(); | 
  | 354 | 74 |                  for (int i = 0; i < atts.getLength(); i++) { | 
  | 355 | 45 |                      String attributeName = atts.getQName(i); | 
  | 356 | 45 |                      String attributeValue = atts.getValue(i); | 
  | 357 | 45 |                      setProperty(constraints, attributeName, attributeValue); | 
  | 358 |  |                  } | 
  | 359 | 29 |                  ColumnConfig lastColumn = null; | 
  | 360 | 29 |                  if (change instanceof ChangeWithColumns) { | 
  | 361 | 29 |                      List<ColumnConfig> columns = ((ChangeWithColumns) change).getColumns(); | 
  | 362 | 29 |                      if (columns != null && columns.size() > 0) { | 
  | 363 | 29 |                          lastColumn = columns.get(columns.size() - 1); | 
  | 364 |  |                      } | 
  | 365 | 29 |                  } else { | 
  | 366 | 0 |                      throw new RuntimeException("Unexpected change: " + change.getClass().getName()); | 
  | 367 |  |                  } | 
  | 368 | 29 |                  if (lastColumn == null) { | 
  | 369 | 0 |                      throw new RuntimeException("Could not determine column to add constraint to"); | 
  | 370 |  |                  } | 
  | 371 | 29 |                  lastColumn.setConstraints(constraints); | 
  | 372 | 29 |              } else if ("param".equals(qName)) { | 
  | 373 | 2 |                  if (change instanceof CustomChangeWrapper) { | 
  | 374 | 2 |                      if (atts.getValue("value") == null) { | 
  | 375 | 1 |                          paramName = atts.getValue("name"); | 
  | 376 | 1 |                          text = new StringBuffer(); | 
  | 377 |  |                      } else { | 
  | 378 | 1 |                          ((CustomChangeWrapper) change).setParam(atts.getValue("name"), atts.getValue("value")); | 
  | 379 |  |                      } | 
  | 380 |  |                  } else { | 
  | 381 | 0 |                      throw new MigrationFailedException(changeSet, "'param' unexpected in " + qName); | 
  | 382 |  |                  } | 
  | 383 | 0 |              } else if ("where".equals(qName)) { | 
  | 384 | 0 |                  text = new StringBuffer(); | 
  | 385 | 0 |              } else if ("property".equals(qName)) { | 
  | 386 | 0 |                  String context = StringUtils.trimToNull(atts.getValue("context")); | 
  | 387 | 0 |                  String dbms = StringUtils.trimToNull(atts.getValue("dbms")); | 
  | 388 | 0 |                  if (StringUtils.trimToNull(atts.getValue("file")) == null) { | 
  | 389 | 0 |                      this.changeLogParameters.set(atts.getValue("name"), atts.getValue("value"), context, dbms); | 
  | 390 |  |                  } else { | 
  | 391 | 0 |                      Properties props = new Properties(); | 
  | 392 | 0 |                      InputStream propertiesStream = resourceAccessor.getResourceAsStream(atts.getValue("file")); | 
  | 393 | 0 |                      if (propertiesStream == null) { | 
  | 394 | 0 |                          log.info("Could not open properties file " + atts.getValue("file")); | 
  | 395 |  |                      } else { | 
  | 396 | 0 |                          props.load(propertiesStream); | 
  | 397 |  |   | 
  | 398 | 0 |                          for (Map.Entry entry : props.entrySet()) { | 
  | 399 | 0 |                              this.changeLogParameters.set(entry.getKey().toString(), entry.getValue().toString(), | 
  | 400 |  |                                      context, dbms); | 
  | 401 |  |                          } | 
  | 402 |  |                      } | 
  | 403 |  |                  } | 
  | 404 | 0 |              } else if (change instanceof ExecuteShellCommandChange && "arg".equals(qName)) { | 
  | 405 | 0 |                  ((ExecuteShellCommandChange) change).addArg(atts.getValue("value")); | 
  | 406 | 0 |              } else if (change != null) { | 
  | 407 | 0 |                  String creatorMethod = "create" + localName.substring(0, 1).toUpperCase() + localName.substring(1); | 
  | 408 |  |   | 
  | 409 |  |                  Object objectToCreateFrom; | 
  | 410 | 0 |                  if (changeSubObjects.size() == 0) { | 
  | 411 | 0 |                      objectToCreateFrom = change; | 
  | 412 |  |                  } else { | 
  | 413 | 0 |                      objectToCreateFrom = changeSubObjects.peek(); | 
  | 414 |  |                  } | 
  | 415 |  |   | 
  | 416 |  |                  Method method; | 
  | 417 |  |                  try { | 
  | 418 | 0 |                      method = objectToCreateFrom.getClass().getMethod(creatorMethod); | 
  | 419 | 0 |                  } catch (NoSuchMethodException e) { | 
  | 420 | 0 |                      throw new MigrationFailedException(changeSet, "Could not find creator method " + creatorMethod | 
  | 421 |  |                              + " for tag: " + qName); | 
  | 422 | 0 |                  } | 
  | 423 | 0 |                  Object subObject = method.invoke(objectToCreateFrom); | 
  | 424 | 0 |                  for (int i = 0; i < atts.getLength(); i++) { | 
  | 425 | 0 |                      String attributeName = atts.getQName(i); | 
  | 426 | 0 |                      String attributeValue = atts.getValue(i); | 
  | 427 | 0 |                      setProperty(subObject, attributeName, attributeValue); | 
  | 428 |  |                  } | 
  | 429 | 0 |                  changeSubObjects.push(subObject); | 
  | 430 |  |   | 
  | 431 | 0 |              } else { | 
  | 432 | 0 |                  throw new MigrationFailedException(changeSet, "Unexpected tag: " + qName); | 
  | 433 |  |              } | 
  | 434 | 0 |          } catch (Exception e) { | 
  | 435 | 0 |              log.severe("Error thrown as a SAXException: " + e.getMessage(), e); | 
  | 436 | 0 |              e.printStackTrace(); | 
  | 437 | 0 |              throw new SAXException(e); | 
  | 438 | 163 |          } | 
  | 439 | 163 |      } | 
  | 440 |  |   | 
  | 441 |  |      protected boolean handleIncludedChangeLog(String fileName, boolean isRelativePath, String relativeBaseFileName) | 
  | 442 |  |              throws LiquibaseException { | 
  | 443 | 6 |          if (!(fileName.endsWith(".xml") || fileName.endsWith(".sql"))) { | 
  | 444 | 0 |              log.debug(relativeBaseFileName + "/" + fileName + " is not a recognized file type"); | 
  | 445 | 0 |              return false; | 
  | 446 |  |          } | 
  | 447 |  |   | 
  | 448 | 6 |          if (fileName.equalsIgnoreCase(".svn") || fileName.equalsIgnoreCase("cvs")) { | 
  | 449 | 0 |              return false; | 
  | 450 |  |          } | 
  | 451 |  |   | 
  | 452 | 6 |          if (isRelativePath) { | 
  | 453 |  |               | 
  | 454 | 3 |              String tempFile = FilenameUtils.concat(FilenameUtils.getFullPath(relativeBaseFileName), fileName); | 
  | 455 | 3 |              if (tempFile != null && new File(tempFile).exists() == true) { | 
  | 456 | 0 |                  fileName = tempFile; | 
  | 457 |  |              } else { | 
  | 458 | 3 |                  fileName = FilenameUtils.getFullPath(relativeBaseFileName) + fileName; | 
  | 459 |  |              } | 
  | 460 |  |          } | 
  | 461 | 6 |          DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance().getParser(fileName, resourceAccessor) | 
  | 462 |  |                  .parse(fileName, changeLogParameters, resourceAccessor); | 
  | 463 | 6 |          PreconditionContainer preconditions = changeLog.getPreconditions(); | 
  | 464 | 6 |          if (preconditions != null) { | 
  | 465 | 6 |              if (null == databaseChangeLog.getPreconditions()) { | 
  | 466 | 0 |                  databaseChangeLog.setPreconditions(new PreconditionContainer()); | 
  | 467 |  |              } | 
  | 468 | 6 |              databaseChangeLog.getPreconditions().addNestedPrecondition(preconditions); | 
  | 469 |  |          } | 
  | 470 | 6 |          for (ChangeSet changeSet : changeLog.getChangeSets()) { | 
  | 471 | 10 |              handleChangeSet(changeSet); | 
  | 472 |  |          } | 
  | 473 |  |   | 
  | 474 | 6 |          return true; | 
  | 475 |  |      } | 
  | 476 |  |   | 
  | 477 |  |      private void setProperty(Object object, String attributeName, String attributeValue) throws IllegalAccessException, | 
  | 478 |  |              InvocationTargetException, CustomChangeException { | 
  | 479 | 173 |          if (object instanceof CustomChangeWrapper) { | 
  | 480 | 1 |              if (attributeName.equals("class")) { | 
  | 481 | 1 |                  ((CustomChangeWrapper) object).setClass(changeLogParameters.expandExpressions(attributeValue)); | 
  | 482 |  |              } else { | 
  | 483 | 0 |                  ((CustomChangeWrapper) object).setParam(attributeName, | 
  | 484 |  |                          changeLogParameters.expandExpressions(attributeValue)); | 
  | 485 |  |              } | 
  | 486 |  |          } else { | 
  | 487 | 172 |              ObjectUtil.setProperty(object, attributeName, changeLogParameters.expandExpressions(attributeValue)); | 
  | 488 |  |          } | 
  | 489 | 173 |      } | 
  | 490 |  |   | 
  | 491 |  |      @Override | 
  | 492 |  |      public void endElement(String uri, String localName, String qName) throws SAXException { | 
  | 493 | 161 |          String textString = null; | 
  | 494 | 161 |          if (text != null && text.length() > 0) { | 
  | 495 | 100 |              textString = changeLogParameters.expandExpressions(StringUtils.trimToNull(text.toString())); | 
  | 496 |  |          } | 
  | 497 |  |   | 
  | 498 |  |          try { | 
  | 499 | 161 |              if (changeSubObjects.size() > 0) { | 
  | 500 | 0 |                  changeSubObjects.pop(); | 
  | 501 | 161 |              } else if (rootPrecondition != null) { | 
  | 502 | 5 |                  if ("preConditions".equals(qName)) { | 
  | 503 | 1 |                      if (changeSet == null) { | 
  | 504 | 1 |                          databaseChangeLog.setPreconditions(rootPrecondition); | 
  | 505 | 1 |                          handlePreCondition(rootPrecondition); | 
  | 506 |  |                      } else { | 
  | 507 | 0 |                          changeSet.setPreconditions(rootPrecondition); | 
  | 508 |  |                      } | 
  | 509 | 1 |                      rootPrecondition = null; | 
  | 510 | 4 |                  } else if ("and".equals(qName)) { | 
  | 511 | 0 |                      preconditionLogicStack.pop(); | 
  | 512 | 0 |                      currentPrecondition = null; | 
  | 513 | 4 |                  } else if ("or".equals(qName)) { | 
  | 514 | 1 |                      preconditionLogicStack.pop(); | 
  | 515 | 1 |                      currentPrecondition = null; | 
  | 516 | 3 |                  } else if ("not".equals(qName)) { | 
  | 517 | 0 |                      preconditionLogicStack.pop(); | 
  | 518 | 0 |                      currentPrecondition = null; | 
  | 519 | 3 |                  } else if (qName.equals("sqlCheck")) { | 
  | 520 | 0 |                      ((SqlPrecondition) currentPrecondition).setSql(textString); | 
  | 521 | 0 |                      currentPrecondition = null; | 
  | 522 | 3 |                  } else if (qName.equals("customPrecondition")) { | 
  | 523 | 0 |                      ((CustomPreconditionWrapper) currentPrecondition).setClassLoader(resourceAccessor.toClassLoader()); | 
  | 524 | 0 |                      currentPrecondition = null; | 
  | 525 |  |                  } | 
  | 526 |  |   | 
  | 527 | 156 |              } else if (changeSet != null && "rollback".equals(qName)) { | 
  | 528 | 1 |                  changeSet.addRollBackSQL(textString); | 
  | 529 | 1 |                  inRollback = false; | 
  | 530 | 155 |              } else if (change != null && change instanceof RawSQLChange && "comment".equals(qName)) { | 
  | 531 | 0 |                  ((RawSQLChange) change).setComments(textString); | 
  | 532 | 0 |                  text = new StringBuffer(); | 
  | 533 | 155 |              } else if (change != null && "where".equals(qName)) { | 
  | 534 | 0 |                  if (change instanceof UpdateDataChange) { | 
  | 535 | 0 |                      ((UpdateDataChange) change).setWhereClause(textString); | 
  | 536 | 0 |                  } else if (change instanceof DeleteDataChange) { | 
  | 537 | 0 |                      ((DeleteDataChange) change).setWhereClause(textString); | 
  | 538 |  |                  } else { | 
  | 539 | 0 |                      throw new RuntimeException("Unexpected change type: " + change.getClass().getName()); | 
  | 540 |  |                  } | 
  | 541 | 0 |                  text = new StringBuffer(); | 
  | 542 | 155 |              } else if (change != null && change instanceof CreateProcedureChange && "comment".equals(qName)) { | 
  | 543 | 0 |                  ((CreateProcedureChange) change).setComments(textString); | 
  | 544 | 0 |                  text = new StringBuffer(); | 
  | 545 | 155 |              } else if (change != null && change instanceof CustomChangeWrapper && paramName != null | 
  | 546 |  |                      && "param".equals(qName)) { | 
  | 547 | 1 |                  ((CustomChangeWrapper) change).setParam(paramName, textString); | 
  | 548 | 1 |                  text = new StringBuffer(); | 
  | 549 | 1 |                  paramName = null; | 
  | 550 | 154 |              } else if (changeSet != null && "comment".equals(qName)) { | 
  | 551 | 7 |                  changeSet.setComments(textString); | 
  | 552 | 7 |                  text = new StringBuffer(); | 
  | 553 | 147 |              } else if (changeSet != null && "changeSet".equals(qName)) { | 
  | 554 | 22 |                  handleChangeSet(changeSet); | 
  | 555 | 22 |                  changeSet = null; | 
  | 556 | 125 |              } else if (change != null && qName.equals("column") && textString != null) { | 
  | 557 | 0 |                  if (change instanceof InsertDataChange) { | 
  | 558 | 0 |                      List<ColumnConfig> columns = ((InsertDataChange) change).getColumns(); | 
  | 559 | 0 |                      columns.get(columns.size() - 1).setValue(textString); | 
  | 560 | 0 |                  } else if (change instanceof UpdateDataChange) { | 
  | 561 | 0 |                      List<ColumnConfig> columns = ((UpdateDataChange) change).getColumns(); | 
  | 562 | 0 |                      columns.get(columns.size() - 1).setValue(textString); | 
  | 563 | 0 |                  } else { | 
  | 564 | 0 |                      throw new RuntimeException("Unexpected column with text: " + textString); | 
  | 565 |  |                  } | 
  | 566 | 0 |                  this.text = new StringBuffer(); | 
  | 567 | 125 |              } else if (change != null && localName.equals(change.getChangeMetaData().getName())) { | 
  | 568 | 23 |                  if (textString != null) { | 
  | 569 | 0 |                      if (change instanceof RawSQLChange) { | 
  | 570 | 0 |                          ((RawSQLChange) change).setSql(textString); | 
  | 571 | 0 |                      } else if (change instanceof CreateProcedureChange) { | 
  | 572 | 0 |                          ((CreateProcedureChange) change).setProcedureBody(textString); | 
  | 573 |  |                           | 
  | 574 |  |                           | 
  | 575 |  |                           | 
  | 576 | 0 |                      } else if (change instanceof CreateViewChange) { | 
  | 577 | 0 |                          ((CreateViewChange) change).setSelectQuery(textString); | 
  | 578 | 0 |                      } else if (change instanceof StopChange) { | 
  | 579 | 0 |                          ((StopChange) change).setMessage(textString); | 
  | 580 |  |                      } else { | 
  | 581 | 0 |                          throw new RuntimeException("Unexpected text in " + change.getChangeMetaData().getName()); | 
  | 582 |  |                      } | 
  | 583 |  |                  } | 
  | 584 | 23 |                  text = null; | 
  | 585 | 23 |                  if (inRollback) { | 
  | 586 | 0 |                      changeSet.addRollbackChange(change); | 
  | 587 |  |                  } else { | 
  | 588 | 23 |                      changeSet.addChange(change); | 
  | 589 |  |                  } | 
  | 590 | 23 |                  change = null; | 
  | 591 | 102 |              } else if (changeSet != null && "validCheckSum".equals(qName)) { | 
  | 592 | 0 |                  changeSet.addValidCheckSum(text.toString()); | 
  | 593 | 0 |                  text = null; | 
  | 594 | 102 |              } else if ("modifySql".equals(qName)) { | 
  | 595 | 0 |                  inModifySql = false; | 
  | 596 | 0 |                  modifySqlDbmsList = null; | 
  | 597 | 0 |                  modifySqlContexts = null; | 
  | 598 | 0 |                  modifySqlAppliedOnRollback = false; | 
  | 599 |  |              } | 
  | 600 | 0 |          } catch (Exception e) { | 
  | 601 | 0 |              log.severe("Error thrown as a SAXException: " + e.getMessage(), e); | 
  | 602 | 0 |              throw new SAXException(databaseChangeLog.getPhysicalFilePath() + ": " + e.getMessage(), e); | 
  | 603 | 161 |          } | 
  | 604 | 161 |      } | 
  | 605 |  |   | 
  | 606 |  |      protected void handlePreCondition(@SuppressWarnings("unused") Precondition precondition) { | 
  | 607 | 1 |          databaseChangeLog.setPreconditions(rootPrecondition); | 
  | 608 | 1 |      } | 
  | 609 |  |   | 
  | 610 |  |      protected void handleChangeSet(ChangeSet changeSet) { | 
  | 611 | 32 |          databaseChangeLog.addChangeSet(changeSet); | 
  | 612 | 32 |      } | 
  | 613 |  |   | 
  | 614 |  |      @Override | 
  | 615 |  |      public void characters(char ch[], int start, int length) throws SAXException { | 
  | 616 | 68 |          if (text != null) { | 
  | 617 | 68 |              text.append(new String(ch, start, length)); | 
  | 618 |  |          } | 
  | 619 | 68 |      } | 
  | 620 |  |   | 
  | 621 |  |       | 
  | 622 |  |   | 
  | 623 |  |   | 
  | 624 | 163 |      private class ExpandingAttributes implements Attributes { | 
  | 625 |  |          private Attributes attributes; | 
  | 626 |  |   | 
  | 627 | 163 |          private ExpandingAttributes(Attributes attributes) { | 
  | 628 | 163 |              this.attributes = attributes; | 
  | 629 | 163 |          } | 
  | 630 |  |   | 
  | 631 |  |          @Override | 
  | 632 |  |          public int getLength() { | 
  | 633 | 280 |              return attributes.getLength(); | 
  | 634 |  |          } | 
  | 635 |  |   | 
  | 636 |  |          @Override | 
  | 637 |  |          public String getURI(int index) { | 
  | 638 | 0 |              return attributes.getURI(index); | 
  | 639 |  |          } | 
  | 640 |  |   | 
  | 641 |  |          @Override | 
  | 642 |  |          public String getLocalName(int index) { | 
  | 643 | 23 |              return attributes.getLocalName(index); | 
  | 644 |  |          } | 
  | 645 |  |   | 
  | 646 |  |          @Override | 
  | 647 |  |          public String getQName(int index) { | 
  | 648 | 150 |              return attributes.getQName(index); | 
  | 649 |  |          } | 
  | 650 |  |   | 
  | 651 |  |          @Override | 
  | 652 |  |          public String getType(int index) { | 
  | 653 | 0 |              return attributes.getType(index); | 
  | 654 |  |          } | 
  | 655 |  |   | 
  | 656 |  |          @Override | 
  | 657 |  |          public String getValue(int index) { | 
  | 658 | 173 |              return attributes.getValue(index); | 
  | 659 |  |          } | 
  | 660 |  |   | 
  | 661 |  |          @Override | 
  | 662 |  |          public int getIndex(String uri, String localName) { | 
  | 663 | 0 |              return attributes.getIndex(uri, localName); | 
  | 664 |  |          } | 
  | 665 |  |   | 
  | 666 |  |          @Override | 
  | 667 |  |          public int getIndex(String qName) { | 
  | 668 | 0 |              return attributes.getIndex(qName); | 
  | 669 |  |          } | 
  | 670 |  |   | 
  | 671 |  |          @Override | 
  | 672 |  |          public String getType(String uri, String localName) { | 
  | 673 | 0 |              return attributes.getType(uri, localName); | 
  | 674 |  |          } | 
  | 675 |  |   | 
  | 676 |  |          @Override | 
  | 677 |  |          public String getType(String qName) { | 
  | 678 | 0 |              return attributes.getType(qName); | 
  | 679 |  |          } | 
  | 680 |  |   | 
  | 681 |  |          @Override | 
  | 682 |  |          public String getValue(String uri, String localName) { | 
  | 683 | 0 |              return changeLogParameters.expandExpressions(attributes.getValue(uri, localName)); | 
  | 684 |  |          } | 
  | 685 |  |   | 
  | 686 |  |          @Override | 
  | 687 |  |          public String getValue(String qName) { | 
  | 688 | 285 |              return changeLogParameters.expandExpressions(attributes.getValue(qName)); | 
  | 689 |  |          } | 
  | 690 |  |      } | 
  | 691 |  |   | 
  | 692 |  |      static File extractZipFile(URL resource) throws IOException { | 
  | 693 | 0 |          String file = resource.getFile(); | 
  | 694 | 0 |          String path = file.split("!")[0]; | 
  | 695 | 0 |          if (path.matches("file:\\/[A-Za-z]:\\/.*")) { | 
  | 696 | 0 |              path = path.replaceFirst("file:\\/", ""); | 
  | 697 |  |          } else { | 
  | 698 | 0 |              path = path.replaceFirst("file:", ""); | 
  | 699 |  |          } | 
  | 700 | 0 |          path = URLDecoder.decode(path); | 
  | 701 | 0 |          File zipfile = new File(path); | 
  | 702 |  |   | 
  | 703 | 0 |          File tempDir = File.createTempFile("liquibase-sax", ".dir"); | 
  | 704 | 0 |          tempDir.delete(); | 
  | 705 | 0 |          tempDir.mkdir(); | 
  | 706 |  |           | 
  | 707 |  |   | 
  | 708 | 0 |          JarFile jarFile = new JarFile(zipfile); | 
  | 709 | 0 |          Enumeration<JarEntry> entries = jarFile.entries(); | 
  | 710 | 0 |          while (entries.hasMoreElements()) { | 
  | 711 | 0 |              JarEntry entry = entries.nextElement(); | 
  | 712 | 0 |              File entryFile = new File(tempDir, entry.getName()); | 
  | 713 | 0 |              entryFile.mkdirs(); | 
  | 714 | 0 |          } | 
  | 715 |  |   | 
  | 716 | 0 |          return tempDir; | 
  | 717 |  |      } | 
  | 718 |  |  } |