/************************************************************************************** * Copyright (c) Jonas Bon�r, Alexandre Vasseur. All rights reserved. * * http://aspectwerkz.codehaus.org * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the LGPL license * * a copy of which has been included with this distribution in the license.txt file. * **************************************************************************************/ package org.codehaus.aspectwerkz.definition; import org.codehaus.aspectwerkz.exception.DefinitionException; import org.codehaus.aspectwerkz.exception.WrappedRuntimeException; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.DocumentHelper; import org.dom4j.io.SAXReader; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.FileInputStream; import java.io.BufferedInputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Iterator; import java.util.List; import java.util.Set; /** * Parses the XML definition file using <tt>dom4j</tt>. * * @author <a href="mailto:jboner@codehaus.org">Jonas Bon�r </a> */ public class XmlParser { /** * The current DTD public id. The matching dtd will be searched as a resource. */ private final static String DTD_PUBLIC_ID = "-//AspectWerkz//DTD 2.0//EN"; /** * The DTD alias, for better user experience. */ private final static String DTD_PUBLIC_ID_ALIAS = "-//AspectWerkz//DTD//EN"; /** * A handler to the DTD stream so that we are only using one file descriptor */ private final static InputStream DTD_STREAM = XmlParser.class.getResourceAsStream("/aspectwerkz2.dtd"); /** * The timestamp, holding the last time that the definition was parsed. */ private static File s_timestamp = new File(".timestamp"); /** * The AspectWerkz definitions. */ private static Set s_definitions = null; // /** // * Returns the aspect class names defined in the XML file. // * // * @param definitionFile the definition file // * @return the definitions // */ // public static List getAspectClassNames(final File definitionFile) { // if (definitionFile == null) { // throw new IllegalArgumentException("definition file can not be null"); // } // if (!definitionFile.exists()) { // throw new DefinitionException("definition file " + definitionFile.toString() + " does not exist"); // } // try { // return getAspectClassNames(definitionFile.toURL()); // } catch (MalformedURLException e) { // throw new DefinitionException(definitionFile + " does not exist"); // } // } // /** // * Returns the aspect class names defined in the XML file. // * // * @param definitionURL the definition URL // * @return the definitions // */ // public static List getAspectClassNames(final URL definitionURL) { // if (definitionURL == null) { // throw new IllegalArgumentException("definition file can not be null"); // } // try { // Document document = createDocument(definitionURL); // return DocumentParser.parseAspectClassNames(document); // } catch (DocumentException e) { // throw new DefinitionException("XML definition file <" + definitionURL + "> has errors: " + e.toString()); // } // } // /** // * Returns the aspect class names defined in the XML file. // * // * @param stream the input stream containing the document // * @return the definitions // */ // public static List getAspectClassNames(final InputStream stream) { // try { // Document document = createDocument(stream); // return DocumentParser.parseAspectClassNames(document); // } catch (DocumentException e) { // throw new DefinitionException("XML definition file on classpath has errors: " + e.toString()); // } // } // /** // * Parses the XML definition file, only if it has been updated. Uses a timestamp to check for modifications. // * // * @param loader the current class loader // * @param definitionFile the definition file // * @param isDirty flag to mark the the definition as updated or not // * @return the definitions // */ // public static Set parse(final ClassLoader loader, final File definitionFile, boolean isDirty) { // if (definitionFile == null) { // throw new IllegalArgumentException("definition file can not be null"); // } // if (!definitionFile.exists()) { // throw new DefinitionException("definition file " + definitionFile.toString() + " does not exist"); // } // // // if definition is not updated; don't parse but return it right away // if (isNotUpdated(definitionFile)) { // isDirty = false; // return s_definitions; // } // // // updated definition, ready to be parsed // try { // Document document = createDocument(definitionFile.toURL()); // s_definitions = DocumentParser.parse(loader, document); // setParsingTimestamp(); // isDirty = true; // return s_definitions; // } catch (MalformedURLException e) { // throw new DefinitionException(definitionFile + " does not exist"); // } catch (DocumentException e) { // throw new DefinitionException("XML definition file <" + definitionFile + "> has errors: " + e.toString()); // } // } // /** // * Parses the XML definition file retrieved from an input stream. // * // * @param loader the current class loader // * @param stream the input stream containing the document // * @return the definitions // */ // public static Set parse(final ClassLoader loader, final InputStream stream) { // try { // Document document = createDocument(stream); // s_definitions = DocumentParser.parse(loader, document); // return s_definitions; // } catch (DocumentException e) { // throw new DefinitionException("XML definition file on classpath has errors: " + e.getMessage()); // } // } /** * Parses the XML definition file not using the cache. * * @param loader the current class loader * @param url the URL to the definition file * @return the definition object */ public static Set parseNoCache(final ClassLoader loader, final URL url) { try { Document document = createDocument(url); s_definitions = DocumentParser.parse(loader, document); return s_definitions; } catch (Exception e) { throw new WrappedRuntimeException(e); } } /** * Merges two DOM documents. * * @param document1 the first document * @param document2 the second document * @return the definition merged document */ public static Document mergeDocuments(final Document document1, final Document document2) { if ((document2 == null) && (document1 != null)) { return document1; } if ((document1 == null) && (document2 != null)) { return document2; } if ((document1 == null) && (document2 == null)) { return null; } try { Element root1 = document1.getRootElement(); Element root2 = document2.getRootElement(); for (Iterator it1 = root2.elementIterator(); it1.hasNext();) { Element element = (Element) it1.next(); element.setParent(null); root1.add(element); } } catch (Exception e) { throw new WrappedRuntimeException(e); } return document1; } /** * Creates a DOM document. * * @param url the URL to the file containing the XML * @return the DOM document * @throws DocumentException */ public static Document createDocument(final URL url) throws DocumentException { SAXReader reader = new SAXReader(); setEntityResolver(reader); InputStream in = null; try { in = url.openStream(); return reader.read(in); } catch (IOException e) { throw new DocumentException(e); } finally { try {in.close();} catch (Throwable t) {;} } } // /** // * Creates a DOM document. // * // * @param stream the stream containing the XML // * @return the DOM document // * @throws DocumentException // */ // public static Document createDocument(final InputStream stream) throws DocumentException { // SAXReader reader = new SAXReader(); // setEntityResolver(reader); // return reader.read(stream); // } /** * Creates a DOM document. * * @param string the string containing the XML * @return the DOM document * @throws DocumentException */ public static Document createDocument(final String string) throws DocumentException { return DocumentHelper.parseText(string); } /** * Sets the entity resolver which is created based on the DTD from in the root dir of the AspectWerkz distribution. * * @param reader the reader to set the resolver in */ private static void setEntityResolver(final SAXReader reader) { EntityResolver resolver = new EntityResolver() { public InputSource resolveEntity(String publicId, String systemId) { if (publicId.equals(DTD_PUBLIC_ID) || publicId.equals(DTD_PUBLIC_ID_ALIAS)) { InputStream in = DTD_STREAM; if (in == null) { System.err.println("AspectWerkz - WARN - could not open DTD"); return new InputSource(); } else { return new InputSource(in); } } else { System.err.println( "AspectWerkz - WARN - deprecated DTD " + publicId + " - consider upgrading to " + DTD_PUBLIC_ID ); return new InputSource(); // avoid null pointer exception } } }; reader.setEntityResolver(resolver); } /** * Checks if the definition file has been updated since the last parsing. * * @param definitionFile the definition file * @return boolean */ private static boolean isNotUpdated(final File definitionFile) { return (definitionFile.lastModified() < getParsingTimestamp()) && (s_definitions != null); } /** * Sets the timestamp for the latest parsing of the definition file. */ private static void setParsingTimestamp() { final long newModifiedTime = System.currentTimeMillis(); s_timestamp.setLastModified(newModifiedTime); } /** * Returns the timestamp for the last parsing of the definition file. * * @return the timestamp */ private static long getParsingTimestamp() { final long modifiedTime = s_timestamp.lastModified(); if (modifiedTime == 0L) { // no timestamp, create a new one try { s_timestamp.createNewFile(); } catch (IOException e) { throw new RuntimeException("could not create timestamp file: " + s_timestamp.getAbsolutePath()); } } return modifiedTime; } }