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
110 if (isNotUpdated(definitionFile)) {
111 isDirty = false;
112 return s_definitions;
113 }
114
115
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();
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
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 }