View Javadoc

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