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