SqlDiffFileParser.java

/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.module;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * This class will parse an xml sql diff file
 * 
 * @version 1.0
 */
public class SqlDiffFileParser {
	
	private static Log log = LogFactory.getLog(SqlDiffFileParser.class);
	
	/**
	 * Get the diff map. Return a sorted map<version, sql statements>
	 * 
	 * @return SortedMap<String, String>
	 * @throws ModuleException
	 */
	public static SortedMap<String, String> getSqlDiffs(Module module) throws ModuleException {
		if (module == null)
			throw new ModuleException("Module cannot be null");
		
		SortedMap<String, String> map = new TreeMap<String, String>(new VersionComparator());
		
		InputStream diffStream = null;
		
		// get the diff stream
		JarFile jarfile = null;
		try {
			try {
				jarfile = new JarFile(module.getFile());
			}
			catch (IOException e) {
				throw new ModuleException("Unable to get jar file", module.getName(), e);
			}
			
			ZipEntry diffEntry = jarfile.getEntry("sqldiff.xml");
			
			if (diffEntry == null) {
				log.debug("No sqldiff.xml found for module: " + module.getName());
				return map;
			} else {
				try {
					diffStream = jarfile.getInputStream(diffEntry);
				}
				catch (IOException e) {
					throw new ModuleException("Unable to get sql diff file stream", module.getName(), e);
				}
			}
			
			try {
				
				// turn the diff stream into an xml document
				Document diffDoc = null;
				try {
					DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
					DocumentBuilder db = dbf.newDocumentBuilder();
					db.setEntityResolver(new EntityResolver() {
						
						public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
							// When asked to resolve external entities (such as a DTD) we return an InputSource
							// with no data at the end, causing the parser to ignore the DTD.
							return new InputSource(new StringReader(""));
						}
					});
					diffDoc = db.parse(diffStream);
				}
				catch (Exception e) {
					throw new ModuleException("Error parsing diff sqldiff.xml file", module.getName(), e);
				}
				
				Element rootNode = diffDoc.getDocumentElement();
				
				String diffVersion = rootNode.getAttribute("version");
				
				if (!validConfigVersions().contains(diffVersion))
					throw new ModuleException("Invalid config version: " + diffVersion, module.getModuleId());
				
				NodeList diffNodes = getDiffNodes(rootNode, diffVersion);
				
				if (diffNodes != null && diffNodes.getLength() > 0) {
					int i = 0;
					while (i < diffNodes.getLength()) {
						Element el = (Element) diffNodes.item(i++);
						String version = getElement(el, diffVersion, "version");
						String sql = getElement(el, diffVersion, "sql");
						map.put(version, sql);
					}
				}
			}
			catch (ModuleException e) {
				if (diffStream != null) {
					try {
						diffStream.close();
					}
					catch (IOException io) {
						log.error("Error while closing config stream for module: " + module.getModuleId(), io);
					}
				}
				
				// rethrow the moduleException
				throw e;
			}
			
		}
		finally {
			try {
				if (jarfile != null)
					jarfile.close();
			}
			catch (IOException e) {
				log.warn("Unable to close jarfile: " + jarfile.getName());
			}
		}
		return map;
	}
	
	/**
	 * Generic method to get a module tag
	 * 
	 * @param element
	 * @param version
	 * @param tag
	 * @return
	 */
	private static String getElement(Element element, String version, String tag) {
		if (element.getElementsByTagName(tag).getLength() > 0)
			return element.getElementsByTagName(tag).item(0).getTextContent();
		return "";
	}
	
	/**
	 * List of the valid sqldiff versions
	 * 
	 * @return
	 */
	private static List<String> validConfigVersions() {
		List<String> versions = new Vector<String>();
		versions.add("1.0");
		return versions;
	}
	
	/**
	 * Finds the nodes that contain diff information
	 * 
	 * @param element
	 * @param version
	 * @return
	 */
	private static NodeList getDiffNodes(Element element, String version) {
		NodeList diffNodes = null;
		
		if ("1.0".equals(version))
			diffNodes = element.getElementsByTagName("diff");
		
		return diffNodes;
	}
	
}