View Javadoc

1   /***************************************************************************************
2    * Copyright (c) Jonas Bonér, Alexandre Vasseur. All rights reserved.                 *
3    * http://aspectwerkz.codehaus.org                                                    *
4    * ---------------------------------------------------------------------------------- *
5    * The software in this package is published under the terms of the LGPL license      *
6    * a copy of which has been included with this distribution in the license.txt file.  *
7    **************************************************************************************/
8   package org.codehaus.aspectwerkz.definition;
9   
10  import org.codehaus.aspectwerkz.exception.DefinitionException;
11  import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
12  import org.dom4j.Document;
13  import org.dom4j.DocumentException;
14  import org.dom4j.Element;
15  import org.dom4j.io.SAXReader;
16  import org.xml.sax.EntityResolver;
17  import org.xml.sax.InputSource;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.net.MalformedURLException;
23  import java.net.URL;
24  import java.util.Iterator;
25  import java.util.List;
26  
27  /***
28   * Parses the XML definition file using <tt>dom4j</tt>.
29   * 
30   * @author <a href="mailto:jboner@codehaus.org">Jonas Bonér </a>
31   */
32  public class XmlParser {
33      /***
34       * The current DTD public id. The matching dtd will be searched as a resource.
35       */
36      private final static String DTD_PUBLIC_ID = "-//AspectWerkz//DTD 1.0//EN";
37  
38      private final static String DTD_PUBLIC_ID_ALIAS = "-//AspectWerkz//DTD//EN";
39  
40      /***
41       * The timestamp, holding the last time that the definition was parsed.
42       */
43      private static File s_timestamp = new File(".timestamp");
44  
45      /***
46       * The AspectWerkz definitions.
47       */
48      private static List s_definitions = null;
49  
50      /***
51       * @param definitionFile the definition file
52       * @return the definitions
53       */
54      public static List getAspectClassNames(final File definitionFile) {
55          if (definitionFile == null) {
56              throw new IllegalArgumentException("definition file can not be null");
57          }
58          if (!definitionFile.exists()) {
59              throw new DefinitionException("definition file " + definitionFile.toString() + " does not exist");
60          }
61          try {
62              return getAspectClassNames(definitionFile.toURL());
63          } catch (MalformedURLException e) {
64              throw new DefinitionException(definitionFile + " does not exist");
65          }
66      }
67  
68      public static List getAspectClassNames(final URL definitionURL) {
69          if (definitionURL == null) {
70              throw new IllegalArgumentException("definition file can not be null");
71          }
72          try {
73              Document document = createDocument(definitionURL);
74              return DocumentParser.parseAspectClassNames(document);
75          } catch (DocumentException e) {
76              throw new DefinitionException("XML definition file <" + definitionURL + "> has errors: " + e.toString());
77          }
78      }
79  
80      /***
81       * @param stream the input stream containing the document
82       * @return the definitions
83       */
84      public static List getAspectClassNames(final InputStream stream) {
85          try {
86              Document document = createDocument(stream);
87              return DocumentParser.parseAspectClassNames(document);
88          } catch (DocumentException e) {
89              throw new DefinitionException("XML definition file on classpath has errors: " + e.toString());
90          }
91      }
92  
93      /***
94       * Parses the XML definition file, only if it has been updated. Uses a timestamp to check for modifications.
95       * 
96       * @param loader the current class loader
97       * @param definitionFile the definition file
98       * @param isDirty flag to mark the the definition as updated or not
99       * @return the definitions
100      */
101     public static List parse(final ClassLoader loader, final File definitionFile, boolean isDirty) {
102         if (definitionFile == null) {
103             throw new IllegalArgumentException("definition file can not be null");
104         }
105         if (!definitionFile.exists()) {
106             throw new DefinitionException("definition file " + definitionFile.toString() + " does not exist");
107         }
108 
109         // if definition is not updated; don't parse but return it right away
110         if (isNotUpdated(definitionFile)) {
111             isDirty = false;
112             return s_definitions;
113         }
114 
115         // updated definition, ready to be parsed
116         try {
117             Document document = createDocument(definitionFile.toURL());
118             s_definitions = DocumentParser.parse(loader, document);
119             setParsingTimestamp();
120             isDirty = true;
121             return s_definitions;
122         } catch (MalformedURLException e) {
123             throw new DefinitionException(definitionFile + " does not exist");
124         } catch (DocumentException e) {
125             throw new DefinitionException("XML definition file <" + definitionFile + "> has errors: " + e.toString());
126         }
127     }
128 
129     /***
130      * Parses the XML definition file retrieved from an input stream.
131      * 
132      * @param loader the current class loader
133      * @param stream the input stream containing the document
134      * @return the definitions
135      */
136     public static List parse(final ClassLoader loader, final InputStream stream) {
137         try {
138             Document document = createDocument(stream);
139             s_definitions = DocumentParser.parse(loader, document);
140             return s_definitions;
141         } catch (DocumentException e) {
142             throw new DefinitionException("XML definition file on classpath has errors: " + e.getMessage());
143         }
144     }
145 
146     /***
147      * Parses the XML definition file not using the cache.
148      * 
149      * @param loader the current class loader
150      * @param url the URL to the definition file
151      * @return the definition object
152      */
153     public static List parseNoCache(final ClassLoader loader, final URL url) {
154         try {
155             Document document = createDocument(url);
156             s_definitions = DocumentParser.parse(loader, document);
157             return s_definitions;
158         } catch (Exception e) {
159             throw new WrappedRuntimeException(e);
160         }
161     }
162 
163     /***
164      * Merges two DOM documents.
165      * 
166      * @param document1 the first document
167      * @param document2 the second document
168      * @return the definition merged document
169      */
170     public static Document mergeDocuments(final Document document1, final Document document2) {
171         if ((document2 == null) && (document1 != null)) {
172             return document1;
173         }
174         if ((document1 == null) && (document2 != null)) {
175             return document2;
176         }
177         if ((document1 == null) && (document2 == null)) {
178             return null;
179         }
180         try {
181             Element root1 = document1.getRootElement();
182             Element root2 = document2.getRootElement();
183             for (Iterator it1 = root2.elementIterator(); it1.hasNext();) {
184                 Element element = (Element) it1.next();
185                 element.setParent(null);
186                 root1.add(element);
187             }
188         } catch (Exception e) {
189             throw new WrappedRuntimeException(e);
190         }
191         return document1;
192     }
193 
194     /***
195      * Creates a DOM document.
196      * 
197      * @param url the URL to the file containing the XML
198      * @return the DOM document
199      * @throws DocumentException
200      * @throws DocumentException
201      */
202     public static Document createDocument(final URL url) throws DocumentException {
203         SAXReader reader = new SAXReader();
204         setEntityResolver(reader);
205         return reader.read(url);
206     }
207 
208     /***
209      * Creates a DOM document.
210      * 
211      * @param stream the stream containing the XML
212      * @return the DOM document
213      * @throws DocumentException
214      * @throws DocumentException
215      */
216     public static Document createDocument(final InputStream stream) throws DocumentException {
217         SAXReader reader = new SAXReader();
218         setEntityResolver(reader);
219         return reader.read(stream);
220     }
221 
222     /***
223      * Sets the entity resolver which is created based on the DTD from in the root dir of the AspectWerkz distribution.
224      * 
225      * @param reader the reader to set the resolver in
226      */
227     private static void setEntityResolver(final SAXReader reader) {
228         EntityResolver resolver = new EntityResolver() {
229             public InputSource resolveEntity(String publicId, String systemId) {
230                 if (publicId.equals(DTD_PUBLIC_ID) || publicId.equals(DTD_PUBLIC_ID_ALIAS)) {
231                     InputStream in = getClass().getResourceAsStream("/aspectwerkz.dtd");
232                     if (in == null) {
233                         System.err.println("AspectWerkz - WARN - could not open DTD");
234                         return new InputSource();
235                     } else {
236                         return new InputSource(in);
237                     }
238                 } else {
239                     System.err.println("AspectWerkz - WARN - deprecated DTD "
240                         + publicId
241                         + " - consider upgrading to "
242                         + DTD_PUBLIC_ID);
243                     return new InputSource(); // avoid null pointer exception
244                 }
245             }
246         };
247         reader.setEntityResolver(resolver);
248     }
249 
250     /***
251      * Checks if the definition file has been updated since the last parsing.
252      * 
253      * @param definitionFile the definition file
254      * @return boolean
255      */
256     private static boolean isNotUpdated(final File definitionFile) {
257         return (definitionFile.lastModified() < getParsingTimestamp()) && (s_definitions != null);
258     }
259 
260     /***
261      * Sets the timestamp for the latest parsing of the definition file.
262      */
263     private static void setParsingTimestamp() {
264         final long newModifiedTime = System.currentTimeMillis();
265         s_timestamp.setLastModified(newModifiedTime);
266     }
267 
268     /***
269      * Returns the timestamp for the last parsing of the definition file.
270      * 
271      * @return the timestamp
272      */
273     private static long getParsingTimestamp() {
274         final long modifiedTime = s_timestamp.lastModified();
275         if (modifiedTime == 0L) {
276             // no timestamp, create a new one
277             try {
278                 s_timestamp.createNewFile();
279             } catch (IOException e) {
280                 throw new RuntimeException("could not create timestamp file: " + s_timestamp.getAbsolutePath());
281             }
282         }
283         return modifiedTime;
284     }
285 }