View Javadoc

1   /**
2    *
3    * Copyright 2003-2004 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.geronimo.deployment.xml;
19  
20  import java.io.BufferedInputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.net.MalformedURLException;
24  import java.net.URI;
25  import java.util.Vector;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.geronimo.gbean.GBeanInfo;
30  import org.apache.geronimo.gbean.GBeanInfoBuilder;
31  import org.apache.xml.resolver.Catalog;
32  import org.apache.xml.resolver.CatalogEntry;
33  import org.apache.xml.resolver.CatalogException;
34  import org.apache.xml.resolver.CatalogManager;
35  import org.xml.sax.EntityResolver;
36  import org.xml.sax.InputSource;
37  import org.xml.sax.SAXException;
38  
39  /**
40   * Implementation of EntityResolver that looks to the local filesystem.
41   *
42   * The implementation tries to resolve an entity via the following steps:
43   *
44   * <ul>
45   *   <li>using a catalog file</li>
46   *   <li>using a local repository</li>
47   *   <li>using JAR files in Classpath</li>
48   * </ul>
49   *
50   * The catalog resolving is based on the OASIS XML Catalog Standard.
51   * OASIS seems to move it around.  Try
52   * http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=entity
53   * and the list of documents currently at
54   * http://www.oasis-open.org/committees/documents.php?wg_abbrev=entity
55   * An older version may be at
56   * http://www.oasis-open.org/committees/entity/archives/spec-2001-08-01.html
57   * and see http://www.oasis-open.org/html/a401.htm
58   *
59   * @version $Rev: 355877 $ $Date: 2005-12-10 18:48:27 -0800 (Sat, 10 Dec 2005) $
60   */
61  public class LocalEntityResolver implements EntityResolver {
62      /**
63       * used Logger
64       */
65      private static final Log log = LogFactory.getLog(LocalEntityResolver.class);
66  
67      /**
68       * The used Catalog Manager
69       */
70      private final CatalogManager manager = new CatalogManager();
71  
72      /**
73       * the XML Catalog
74       */
75      private Catalog catalog = null;
76  
77      /**
78       * the URI of the catalog file
79       */
80      private URI catalogFileURI = null;
81  
82      /**
83       * Local Repository where DTDs and Schemas are located
84       */
85      private URI localRepositoryURI = null;
86  
87      /**
88       * Flag indicating if this resolver may return null to signal
89       * the parser to open a regular URI connection to the system
90       * identifier. Otherwise an exception is thrown.
91       */
92      private boolean failOnUnresolvable = false;
93  
94  
95      public LocalEntityResolver(URI catalogFileURI, URI localRepositoryURI, boolean failOnUnresolvable) {
96          this.catalogFileURI = catalogFileURI;
97          setLocalRepositoryURI(localRepositoryURI);
98          setFailOnUnresolvable(failOnUnresolvable);
99          init();
100     }
101 
102     /**
103      * Sets the setFailOnUnresolvable flag.
104      *
105      * @param b value (true means that a SAXException is thrown
106      * if the entity could not be resolved)
107      */
108     public void setFailOnUnresolvable(final boolean b) {
109         failOnUnresolvable = b;
110     }
111 
112     public boolean isFailOnUnresolvable() {
113         return failOnUnresolvable;
114     }
115 
116     public void setCatalogFileURI(final URI catalogFileURI) {
117         this.catalogFileURI = catalogFileURI;
118         init();
119     }
120 
121     public URI getCatalogFileURI() {
122         return this.catalogFileURI;
123     }
124 
125     public URI getLocalRepositoryURI() {
126         return localRepositoryURI;
127     }
128 
129     public void setLocalRepositoryURI(URI string) {
130         localRepositoryURI = string;
131     }
132 
133     public void addPublicMapping(final String publicId, final String uri) {
134 
135         Vector args = new Vector();
136         args.add(publicId);
137         args.add(uri);
138 
139         addEntry("PUBLIC", args);
140 
141     }
142 
143     public void addSystemMapping(final String systemId, final String uri) {
144 
145         Vector args = new Vector();
146         args.add(systemId);
147         args.add(uri);
148 
149         addEntry("SYSTEM", args);
150 
151     }
152 
153     /**
154      * Attempt to resolve the entity based on the supplied publicId and systemId.
155      * First the catalog is queried with both publicId and systemId.
156      * Then the local repository is queried with the file name part of the systemId
157      * Then the classpath is queried with  the file name part of the systemId.
158      *
159      * Then, if failOnUnresolvable is true, an exception is thrown: otherwise null is returned.
160      * @param publicId
161      * @param systemId
162      * @return
163      * @throws SAXException
164      * @throws IOException
165      */
166     public InputSource resolveEntity(
167             final String publicId,
168             final String systemId)
169             throws SAXException, IOException {
170 
171         if (log.isTraceEnabled()) {
172             log.trace("start resolving for " + entityMessageString(publicId, systemId));
173         }
174 
175         InputSource source = resolveWithCatalog(publicId, systemId);
176         if (source != null) {
177             return source;
178         }
179 
180         source = resolveWithRepository(publicId, systemId);
181         if (source != null) {
182             return source;
183         }
184 
185         source = resolveWithClasspath(publicId, systemId);
186         if (source != null) {
187             return source;
188         }
189 
190         String message = "could not resolve " + entityMessageString(publicId, systemId);
191 
192         if (failOnUnresolvable) {
193             throw new SAXException(message);
194         } else {
195             log.debug(message);
196         }
197 
198         return null;
199     }
200 
201     /**
202      * Try to resolve using the catalog file
203      *
204      * @param publicId the PublicId
205      * @param systemId the SystemId
206      * @return InputSource if the entity could be resolved. null otherwise
207      * @throws MalformedURLException
208      * @throws IOException
209      */
210     InputSource resolveWithCatalog(
211             final String publicId,
212             final String systemId)
213             throws MalformedURLException, IOException {
214 
215         if (catalogFileURI == null) {
216             return null;
217         }
218 
219         String resolvedSystemId =
220                 catalog.resolvePublic(guaranteeNotNull(publicId), systemId);
221 
222         if (resolvedSystemId != null) {
223             if (log.isTraceEnabled()) {
224                 log.trace("resolved " + entityMessageString(publicId, systemId) + " using the catalog file. result: " + resolvedSystemId);
225             }
226             return new InputSource(resolvedSystemId);
227         }
228 
229         return null;
230     }
231 
232     /**
233      * Try to resolve using the local repository and only the supplied systemID filename.
234      * Any path in the systemID will be removed.
235      *
236      * @param publicId the PublicId
237      * @param systemId the SystemId
238      * @return InputSource if the entity could be resolved. null otherwise
239      */
240     InputSource resolveWithRepository(
241             final String publicId,
242             final String systemId) {
243 
244         if (localRepositoryURI == null) {
245             return null;
246         }
247 
248         String fileName = getSystemIdFileName(systemId);
249 
250         if (fileName == null) {
251             return null;
252         }
253 
254         InputStream inputStream = null;
255         URI resolvedSystemIDURI;
256         try {
257             resolvedSystemIDURI = localRepositoryURI.resolve(fileName);
258             inputStream = resolvedSystemIDURI.toURL().openStream();
259         } catch (IOException e) {
260             return null;
261         } catch (IllegalArgumentException e) {
262             //typically "uri is not absolute"
263             return null;
264         }
265         if (inputStream != null) {
266             if (log.isTraceEnabled()) {
267                 log.trace("resolved " + entityMessageString(publicId, systemId) + "with file relative to " + localRepositoryURI + resolvedSystemIDURI);
268             }
269             return new InputSource(inputStream);
270         } else {
271             return null;
272         }
273         /*
274         String resolvedSystemId = null;
275 
276         File file = new File(localRepositoryURI, fileName);
277         if (file.exists()) {
278             resolvedSystemId = file.getAbsolutePath();
279             if (log.isTraceEnabled()) {
280                 log.trace(
281                         "resolved "
282                         + entityMessageString(publicId, systemId)
283                         + "with file relative to "
284                         + localRepositoryURI
285                         + resolvedSystemId);
286             }
287             return new InputSource(resolvedSystemId);
288         }
289         return null;
290         */
291     }
292 
293     /**
294      * Try to resolve using the the classpath and only the supplied systemID.
295      * Any path in the systemID will be removed.
296      *
297      * @param publicId the PublicId
298      * @param systemId the SystemId
299      * @return InputSource if the entity could be resolved. null otherwise
300      */
301     InputSource resolveWithClasspath(
302             final String publicId,
303             final String systemId) {
304 
305         String fileName = getSystemIdFileName(systemId);
306 
307         if (fileName == null) {
308             return null;
309         }
310 
311         InputStream in =
312                 getClass().getClassLoader().getResourceAsStream(fileName);
313         if (in != null) {
314             if (log.isTraceEnabled()) {
315                 log.trace("resolved " + entityMessageString(publicId, systemId) + " via file found file on classpath: " + fileName);
316             }
317             InputSource is = new InputSource(new BufferedInputStream(in));
318             is.setSystemId(systemId);
319             return is;
320         }
321 
322         return null;
323     }
324 
325     /**
326      * Guarantees a not null value
327      */
328     private String guaranteeNotNull(final String string) {
329         return string != null ? string : "";
330     }
331 
332     /**
333      * Returns the SystemIds filename
334      *
335      * @param systemId SystemId
336      * @return filename
337      */
338     private String getSystemIdFileName(final String systemId) {
339 
340         if (systemId == null) {
341             return null;
342         }
343 
344         int indexBackSlash = systemId.lastIndexOf("\\");
345         int indexSlash = systemId.lastIndexOf("/");
346 
347         int index = Math.max(indexBackSlash, indexSlash);
348 
349         String fileName = systemId.substring(index + 1);
350         return fileName;
351     }
352 
353     /**
354      * Constructs a debugging message string
355      */
356     private String entityMessageString(
357             final String publicId,
358             final String systemId) {
359 
360         StringBuffer buffer = new StringBuffer("entity with publicId '");
361         buffer.append(publicId);
362         buffer.append("' and systemId '");
363         buffer.append(systemId);
364         buffer.append("'");
365         return buffer.toString();
366 
367     }
368 
369     /**
370      * Adds a new Entry to the catalog
371      */
372     private void addEntry(String type, Vector args) {
373         try {
374             CatalogEntry entry = new CatalogEntry(type, args);
375             catalog.addEntry(entry);
376         } catch (CatalogException e) {
377             throw new RuntimeException(e);
378         }
379     }
380 
381     /**
382      * Loads mappings from configuration file
383      */
384     private void init() {
385 
386         if (log.isDebugEnabled()) {
387             log.debug("init catalog file " + this.catalogFileURI);
388         }
389 
390         manager.setUseStaticCatalog(false);
391         manager.setCatalogFiles(this.catalogFileURI.toString());
392         manager.setIgnoreMissingProperties(true);
393         catalog = manager.getCatalog();
394     }
395 
396     public static final GBeanInfo GBEAN_INFO;
397 
398     static {
399         GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic("configurable local entity resolver", LocalEntityResolver.class);
400 
401         infoFactory.addAttribute("catalogFileURI", URI.class, true);
402         infoFactory.addAttribute("localRepositoryURI", URI.class, true);
403         infoFactory.addAttribute("failOnUnresolvable", boolean.class, true);
404 
405         infoFactory.addOperation("resolveEntity", new Class[]{String.class, String.class});
406         infoFactory.addOperation("addPublicMapping", new Class[]{String.class, String.class});
407         infoFactory.addOperation("addSystemMapping", new Class[]{String.class, String.class});
408 
409         infoFactory.setConstructor(new String[]{"catalogFileURI", "localRepositoryURI", "failOnUnresolvable"});
410 
411         GBEAN_INFO = infoFactory.getBeanInfo();
412     }
413 
414     public static GBeanInfo getGBeanInfo() {
415         return GBEAN_INFO;
416     }
417 
418 }