001: /**************************************************************************************
002: * Copyright (c) Jonas BonŽr, Alexandre Vasseur. All rights reserved. *
003: * http://aspectwerkz.codehaus.org *
004: * ---------------------------------------------------------------------------------- *
005: * The software in this package is published under the terms of the LGPL license *
006: * a copy of which has been included with this distribution in the license.txt file. *
007: **************************************************************************************/package org.codehaus.aspectwerkz.definition;
008:
009: import org.codehaus.aspectwerkz.exception.DefinitionException;
010: import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
011: import org.dom4j.Document;
012: import org.dom4j.DocumentException;
013: import org.dom4j.Element;
014: import org.dom4j.DocumentHelper;
015: import org.dom4j.io.SAXReader;
016: import org.xml.sax.EntityResolver;
017: import org.xml.sax.InputSource;
018:
019: import java.io.File;
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.io.FileInputStream;
023: import java.io.BufferedInputStream;
024: import java.net.MalformedURLException;
025: import java.net.URL;
026: import java.util.Iterator;
027: import java.util.List;
028: import java.util.Set;
029:
030: /**
031: * Parses the XML definition file using <tt>dom4j</tt>.
032: *
033: * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
034: */
035: public class XmlParser {
036: /**
037: * The current DTD public id. The matching dtd will be searched as a resource.
038: */
039: private final static String DTD_PUBLIC_ID = "-//AspectWerkz//DTD 2.0//EN";
040:
041: /**
042: * The DTD alias, for better user experience.
043: */
044: private final static String DTD_PUBLIC_ID_ALIAS = "-//AspectWerkz//DTD//EN";
045:
046: /**
047: * A handler to the DTD stream so that we are only using one file descriptor
048: */
049: private final static InputStream DTD_STREAM = XmlParser.class
050: .getResourceAsStream("/aspectwerkz2.dtd");
051:
052: /**
053: * The timestamp, holding the last time that the definition was parsed.
054: */
055: private static File s_timestamp = new File(".timestamp");
056:
057: /**
058: * The AspectWerkz definitions.
059: */
060: private static Set s_definitions = null;
061:
062: // /**
063: // * Returns the aspect class names defined in the XML file.
064: // *
065: // * @param definitionFile the definition file
066: // * @return the definitions
067: // */
068: // public static List getAspectClassNames(final File definitionFile) {
069: // if (definitionFile == null) {
070: // throw new IllegalArgumentException("definition file can not be null");
071: // }
072: // if (!definitionFile.exists()) {
073: // throw new DefinitionException("definition file " + definitionFile.toString() + " does not exist");
074: // }
075: // try {
076: // return getAspectClassNames(definitionFile.toURL());
077: // } catch (MalformedURLException e) {
078: // throw new DefinitionException(definitionFile + " does not exist");
079: // }
080: // }
081:
082: // /**
083: // * Returns the aspect class names defined in the XML file.
084: // *
085: // * @param definitionURL the definition URL
086: // * @return the definitions
087: // */
088: // public static List getAspectClassNames(final URL definitionURL) {
089: // if (definitionURL == null) {
090: // throw new IllegalArgumentException("definition file can not be null");
091: // }
092: // try {
093: // Document document = createDocument(definitionURL);
094: // return DocumentParser.parseAspectClassNames(document);
095: // } catch (DocumentException e) {
096: // throw new DefinitionException("XML definition file <" + definitionURL + "> has errors: " + e.toString());
097: // }
098: // }
099:
100: // /**
101: // * Returns the aspect class names defined in the XML file.
102: // *
103: // * @param stream the input stream containing the document
104: // * @return the definitions
105: // */
106: // public static List getAspectClassNames(final InputStream stream) {
107: // try {
108: // Document document = createDocument(stream);
109: // return DocumentParser.parseAspectClassNames(document);
110: // } catch (DocumentException e) {
111: // throw new DefinitionException("XML definition file on classpath has errors: " + e.toString());
112: // }
113: // }
114:
115: // /**
116: // * Parses the XML definition file, only if it has been updated. Uses a timestamp to check for modifications.
117: // *
118: // * @param loader the current class loader
119: // * @param definitionFile the definition file
120: // * @param isDirty flag to mark the the definition as updated or not
121: // * @return the definitions
122: // */
123: // public static Set parse(final ClassLoader loader, final File definitionFile, boolean isDirty) {
124: // if (definitionFile == null) {
125: // throw new IllegalArgumentException("definition file can not be null");
126: // }
127: // if (!definitionFile.exists()) {
128: // throw new DefinitionException("definition file " + definitionFile.toString() + " does not exist");
129: // }
130: //
131: // // if definition is not updated; don't parse but return it right away
132: // if (isNotUpdated(definitionFile)) {
133: // isDirty = false;
134: // return s_definitions;
135: // }
136: //
137: // // updated definition, ready to be parsed
138: // try {
139: // Document document = createDocument(definitionFile.toURL());
140: // s_definitions = DocumentParser.parse(loader, document);
141: // setParsingTimestamp();
142: // isDirty = true;
143: // return s_definitions;
144: // } catch (MalformedURLException e) {
145: // throw new DefinitionException(definitionFile + " does not exist");
146: // } catch (DocumentException e) {
147: // throw new DefinitionException("XML definition file <" + definitionFile + "> has errors: " + e.toString());
148: // }
149: // }
150:
151: // /**
152: // * Parses the XML definition file retrieved from an input stream.
153: // *
154: // * @param loader the current class loader
155: // * @param stream the input stream containing the document
156: // * @return the definitions
157: // */
158: // public static Set parse(final ClassLoader loader, final InputStream stream) {
159: // try {
160: // Document document = createDocument(stream);
161: // s_definitions = DocumentParser.parse(loader, document);
162: // return s_definitions;
163: // } catch (DocumentException e) {
164: // throw new DefinitionException("XML definition file on classpath has errors: " + e.getMessage());
165: // }
166: // }
167:
168: /**
169: * Parses the XML definition file not using the cache.
170: *
171: * @param loader the current class loader
172: * @param url the URL to the definition file
173: * @return the definition object
174: */
175: public static Set parseNoCache(final ClassLoader loader,
176: final URL url) {
177: try {
178: Document document = createDocument(url);
179: s_definitions = DocumentParser.parse(loader, document);
180: return s_definitions;
181: } catch (Exception e) {
182: throw new WrappedRuntimeException(e);
183: }
184: }
185:
186: /**
187: * Merges two DOM documents.
188: *
189: * @param document1 the first document
190: * @param document2 the second document
191: * @return the definition merged document
192: */
193: public static Document mergeDocuments(final Document document1,
194: final Document document2) {
195: if ((document2 == null) && (document1 != null)) {
196: return document1;
197: }
198: if ((document1 == null) && (document2 != null)) {
199: return document2;
200: }
201: if ((document1 == null) && (document2 == null)) {
202: return null;
203: }
204: try {
205: Element root1 = document1.getRootElement();
206: Element root2 = document2.getRootElement();
207: for (Iterator it1 = root2.elementIterator(); it1.hasNext();) {
208: Element element = (Element) it1.next();
209: element.setParent(null);
210: root1.add(element);
211: }
212: } catch (Exception e) {
213: throw new WrappedRuntimeException(e);
214: }
215: return document1;
216: }
217:
218: /**
219: * Creates a DOM document.
220: *
221: * @param url the URL to the file containing the XML
222: * @return the DOM document
223: * @throws DocumentException
224: */
225: public static Document createDocument(final URL url)
226: throws DocumentException {
227: SAXReader reader = new SAXReader();
228: setEntityResolver(reader);
229: InputStream in = null;
230: try {
231: in = url.openStream();
232: return reader.read(in);
233: } catch (IOException e) {
234: throw new DocumentException(e);
235: } finally {
236: try {
237: in.close();
238: } catch (Throwable t) {
239: ;
240: }
241: }
242: }
243:
244: // /**
245: // * Creates a DOM document.
246: // *
247: // * @param stream the stream containing the XML
248: // * @return the DOM document
249: // * @throws DocumentException
250: // */
251: // public static Document createDocument(final InputStream stream) throws DocumentException {
252: // SAXReader reader = new SAXReader();
253: // setEntityResolver(reader);
254: // return reader.read(stream);
255: // }
256:
257: /**
258: * Creates a DOM document.
259: *
260: * @param string the string containing the XML
261: * @return the DOM document
262: * @throws DocumentException
263: */
264: public static Document createDocument(final String string)
265: throws DocumentException {
266: return DocumentHelper.parseText(string);
267: }
268:
269: /**
270: * Sets the entity resolver which is created based on the DTD from in the root dir of the AspectWerkz distribution.
271: *
272: * @param reader the reader to set the resolver in
273: */
274: private static void setEntityResolver(final SAXReader reader) {
275: EntityResolver resolver = new EntityResolver() {
276: public InputSource resolveEntity(String publicId,
277: String systemId) {
278: if (publicId.equals(DTD_PUBLIC_ID)
279: || publicId.equals(DTD_PUBLIC_ID_ALIAS)) {
280: InputStream in = DTD_STREAM;
281: if (in == null) {
282: System.err
283: .println("AspectWerkz - WARN - could not open DTD");
284: return new InputSource();
285: } else {
286: return new InputSource(in);
287: }
288: } else {
289: System.err
290: .println("AspectWerkz - WARN - deprecated DTD "
291: + publicId
292: + " - consider upgrading to "
293: + DTD_PUBLIC_ID);
294: return new InputSource(); // avoid null pointer exception
295: }
296: }
297: };
298: reader.setEntityResolver(resolver);
299: }
300:
301: /**
302: * Checks if the definition file has been updated since the last parsing.
303: *
304: * @param definitionFile the definition file
305: * @return boolean
306: */
307: private static boolean isNotUpdated(final File definitionFile) {
308: return (definitionFile.lastModified() < getParsingTimestamp())
309: && (s_definitions != null);
310: }
311:
312: /**
313: * Sets the timestamp for the latest parsing of the definition file.
314: */
315: private static void setParsingTimestamp() {
316: final long newModifiedTime = System.currentTimeMillis();
317: s_timestamp.setLastModified(newModifiedTime);
318: }
319:
320: /**
321: * Returns the timestamp for the last parsing of the definition file.
322: *
323: * @return the timestamp
324: */
325: private static long getParsingTimestamp() {
326: final long modifiedTime = s_timestamp.lastModified();
327: if (modifiedTime == 0L) {
328: // no timestamp, create a new one
329: try {
330: s_timestamp.createNewFile();
331: } catch (IOException e) {
332: throw new RuntimeException(
333: "could not create timestamp file: "
334: + s_timestamp.getAbsolutePath());
335: }
336: }
337: return modifiedTime;
338: }
339: }
|