View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  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  package org.apache.commons.configuration.resolver;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.net.FileNameMap;
22  import java.net.URL;
23  import java.net.URLConnection;
24  import java.util.Vector;
25  
26  import org.apache.commons.configuration.ConfigurationException;
27  import org.apache.commons.configuration.ConfigurationUtils;
28  import org.apache.commons.configuration.FileSystem;
29  import org.apache.commons.lang.text.StrSubstitutor;
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.xml.resolver.CatalogException;
33  import org.apache.xml.resolver.readers.CatalogReader;
34  import org.xml.sax.EntityResolver;
35  import org.xml.sax.InputSource;
36  import org.xml.sax.SAXException;
37  
38  /**
39   * Thin wrapper around xml commons CatalogResolver to allow list of catalogs
40   * to be provided.
41   * @author <a
42   * href="http://commons.apache.org/configuration/team-list.html">Commons
43   * Configuration team</a>
44   * @since 1.7
45   * @version $Id: CatalogResolver.java 1301991 2012-03-17 20:18:02Z sebb $
46   */
47  public class CatalogResolver implements EntityResolver
48  {
49      /**
50       * Debug everything.
51       */
52      private static final int DEBUG_ALL = 9;
53  
54      /**
55       * Normal debug setting.
56       */
57      private static final int DEBUG_NORMAL = 4;
58  
59      /**
60       * Debug nothing.
61       */
62      private static final int DEBUG_NONE = 0;
63  
64      /**
65       * The CatalogManager
66       */
67      protected CatalogManager manager = new CatalogManager();
68  
69      /**
70       * The FileSystem in use.
71       */
72      protected FileSystem fs = FileSystem.getDefaultFileSystem();
73  
74      /**
75       * The CatalogResolver
76       */
77      private org.apache.xml.resolver.tools.CatalogResolver resolver;
78  
79      /**
80       * Stores the logger.
81       */
82      private Log log;
83  
84      /**
85       * Constructs the CatalogResolver
86       */
87      public CatalogResolver()
88      {
89          manager.setIgnoreMissingProperties(true);
90          manager.setUseStaticCatalog(false);
91          manager.setFileSystem(fs);
92          setLogger(null);
93      }
94  
95      /**
96       * Set the list of catalog file names
97       *
98       * @param catalogs The delimited list of catalog files.
99       */
100     public void setCatalogFiles(String catalogs)
101     {
102         manager.setCatalogFiles(catalogs);
103     }
104 
105     /**
106      * Set the FileSystem.
107      * @param fileSystem The FileSystem.
108      */
109     public void setFileSystem(FileSystem fileSystem)
110     {
111         this.fs = fileSystem;
112         manager.setFileSystem(fileSystem);
113     }
114 
115     /**
116      * Set the base path.
117      * @param baseDir The base path String.
118      */
119     public void setBaseDir(String baseDir)
120     {
121         manager.setBaseDir(baseDir);
122     }
123 
124     /**
125      * Set the StrSubstitutor.
126      * @param substitutor The StrSubstitutor.
127      */
128     public void setSubstitutor(StrSubstitutor substitutor)
129     {
130         manager.setSubstitutor(substitutor);
131     }
132 
133     /**
134      * Enables debug logging of xml-commons Catalog processing.
135      * @param debug True if debugging should be enabled, false otherwise.
136      */
137     public void setDebug(boolean debug)
138     {
139         if (debug)
140         {
141             manager.setVerbosity(DEBUG_ALL);
142         }
143         else
144         {
145             manager.setVerbosity(DEBUG_NONE);
146         }
147     }
148 
149     /**
150      * Implements the {@code resolveEntity} method
151      * for the SAX interface.
152      * <p/>
153      * <p>Presented with an optional public identifier and a system
154      * identifier, this function attempts to locate a mapping in the
155      * catalogs.</p>
156      * <p/>
157      * <p>If such a mapping is found, the resolver attempts to open
158      * the mapped value as an InputSource and return it. Exceptions are
159      * ignored and null is returned if the mapped value cannot be opened
160      * as an input source.</p>
161      * <p/>
162      * <p>If no mapping is found (or an error occurs attempting to open
163      * the mapped value as an input source), null is returned and the system
164      * will use the specified system identifier as if no entityResolver
165      * was specified.</p>
166      *
167      * @param publicId The public identifier for the entity in question.
168      *                 This may be null.
169      * @param systemId The system identifier for the entity in question.
170      *                 XML requires a system identifier on all external entities, so this
171      *                 value is always specified.
172      * @return An InputSource for the mapped identifier, or null.
173      * @throws SAXException if an error occurs.
174      */
175     public InputSource resolveEntity(String publicId, String systemId)
176             throws SAXException
177     {
178         String resolved = getResolver().getResolvedEntity(publicId, systemId);
179 
180         if (resolved != null)
181         {
182             String badFilePrefix = "file://";
183             String correctFilePrefix = "file:///";
184 
185             // Java 5 has a bug when constructing file URLS
186             if (resolved.startsWith(badFilePrefix) && !resolved.startsWith(correctFilePrefix))
187             {
188                 resolved = correctFilePrefix + resolved.substring(badFilePrefix.length());
189             }
190 
191             try
192             {
193                 InputStream is = fs.getInputStream(null, resolved);
194                 InputSource iSource = new InputSource(resolved);
195                 iSource.setPublicId(publicId);
196                 iSource.setByteStream(is);
197                 return iSource;
198             }
199             catch (Exception e)
200             {
201                 log.warn("Failed to create InputSource for " + resolved + " ("
202                                 + e.toString() + ")");
203                 return null;
204             }
205         }
206 
207         return null;
208     }
209 
210     /**
211      * Returns the logger used by this configuration object.
212      *
213      * @return the logger
214      */
215     public Log getLogger()
216     {
217         return log;
218     }
219 
220     /**
221      * Allows to set the logger to be used by this configuration object. This
222      * method makes it possible for clients to exactly control logging behavior.
223      * Per default a logger is set that will ignore all log messages. Derived
224      * classes that want to enable logging should call this method during their
225      * initialization with the logger to be used.
226      *
227      * @param log the new logger
228      */
229     public void setLogger(Log log)
230     {
231         this.log = (log != null) ? log : LogFactory.getLog(CatalogResolver.class);
232     }
233 
234     private synchronized org.apache.xml.resolver.tools.CatalogResolver getResolver()
235     {
236         if (resolver == null)
237         {
238             resolver = new org.apache.xml.resolver.tools.CatalogResolver(manager);
239         }
240         return resolver;
241     }
242 
243     /**
244      * Extend the CatalogManager to make the FileSystem and base directory accessible.
245      */
246     public static class CatalogManager extends org.apache.xml.resolver.CatalogManager
247     {
248         /** The static catalog used by this manager. */
249         private static org.apache.xml.resolver.Catalog staticCatalog;
250 
251         /** The FileSystem */
252         private FileSystem fs;
253 
254         /** The base directory */
255         private String baseDir = System.getProperty("user.dir");
256 
257         /** The String Substitutor */
258         private StrSubstitutor substitutor;
259 
260         /**
261          * Set the FileSystem
262          * @param fileSystem The FileSystem in use.
263          */
264         public void setFileSystem(FileSystem fileSystem)
265         {
266             this.fs = fileSystem;
267         }
268 
269         /**
270          * Retrieve the FileSystem.
271          * @return The FileSystem.
272          */
273         public FileSystem getFileSystem()
274         {
275             return this.fs;
276         }
277 
278         /**
279          * Set the base directory.
280          * @param baseDir The base directory.
281          */
282         public void setBaseDir(String baseDir)
283         {
284             if (baseDir != null)
285             {
286                 this.baseDir = baseDir;
287             }
288         }
289 
290         /**
291          * Return the base directory.
292          * @return The base directory.
293          */
294         public String getBaseDir()
295         {
296             return this.baseDir;
297         }
298 
299         public void setSubstitutor(StrSubstitutor substitutor)
300         {
301             this.substitutor = substitutor;
302         }
303 
304         public StrSubstitutor getStrSubstitutor()
305         {
306             return this.substitutor;
307         }
308 
309 
310         /**
311          * Get a new catalog instance. This method is only overridden because xml-resolver
312          * might be in a parent ClassLoader and will be incapable of loading our Catalog
313          * implementation.
314          *
315          * This method always returns a new instance of the underlying catalog class.
316          * @return the Catalog.
317          */
318         @Override
319         public org.apache.xml.resolver.Catalog getPrivateCatalog()
320         {
321             org.apache.xml.resolver.Catalog catalog = staticCatalog;
322 
323             if (catalog == null || !getUseStaticCatalog())
324             {
325                 try
326                 {
327                     catalog = new Catalog();
328                     catalog.setCatalogManager(this);
329                     catalog.setupReaders();
330                     catalog.loadSystemCatalogs();
331                 }
332                 catch (Exception ex)
333                 {
334                     ex.printStackTrace();
335                 }
336 
337                 if (getUseStaticCatalog())
338                 {
339                     staticCatalog = catalog;
340                 }
341             }
342 
343             return catalog;
344         }
345 
346         /**
347          * Get a catalog instance.
348          *
349          * If this manager uses static catalogs, the same static catalog will
350          * always be returned. Otherwise a new catalog will be returned.
351          * @return The Catalog.
352          */
353         @Override
354         public org.apache.xml.resolver.Catalog getCatalog()
355         {
356             return getPrivateCatalog();
357         }
358     }
359 
360     /**
361      * Overrides the Catalog implementation to use the underlying FileSystem.
362      */
363     public static class Catalog extends org.apache.xml.resolver.Catalog
364     {
365         /** The FileSystem */
366         private FileSystem fs;
367 
368         /** FileNameMap to determine the mime type */
369         private FileNameMap fileNameMap = URLConnection.getFileNameMap();
370 
371         /**
372          * Load the catalogs.
373          * @throws IOException if an error occurs.
374          */
375         @Override
376         public void loadSystemCatalogs() throws IOException
377         {
378             fs = ((CatalogManager) catalogManager).getFileSystem();
379             String base = ((CatalogManager) catalogManager).getBaseDir();
380 
381             // This is safe because the catalog manager returns a vector of strings.
382             @SuppressWarnings("unchecked")
383             Vector<String> catalogs = catalogManager.getCatalogFiles();
384             if (catalogs != null)
385             {
386                 for (int count = 0; count < catalogs.size(); count++)
387                 {
388                     String fileName = catalogs.elementAt(count);
389 
390                     URL url = null;
391                     InputStream is = null;
392 
393                     try
394                     {
395                         url = ConfigurationUtils.locate(fs, base, fileName);
396                         if (url != null)
397                         {
398                             is = fs.getInputStream(url);
399                         }
400                     }
401                     catch (ConfigurationException ce)
402                     {
403                         String name = (url == null) ? fileName : url.toString();
404                         // Ignore the exception.
405                         catalogManager.debug.message(DEBUG_ALL,
406                             "Unable to get input stream for " + name + ". " + ce.getMessage());
407                     }
408                     if (is != null)
409                     {
410                         String mimeType = fileNameMap.getContentTypeFor(fileName);
411                         try
412                         {
413                             if (mimeType != null)
414                             {
415                                 parseCatalog(mimeType, is);
416                                 continue;
417                             }
418                         }
419                         catch (Exception ex)
420                         {
421                             // Ignore the exception.
422                             catalogManager.debug.message(DEBUG_ALL,
423                                 "Exception caught parsing input stream for " + fileName + ". "
424                                 + ex.getMessage());
425                         }
426                         finally
427                         {
428                             is.close();
429                         }
430                     }
431                     parseCatalog(base, fileName);
432                 }
433             }
434 
435         }
436 
437         /**
438          * Parse the specified catalog file.
439          * @param baseDir The base directory, if not included in the file name.
440          * @param fileName The catalog file. May be a full URI String.
441          * @throws IOException If an error occurs.
442          */
443         public void parseCatalog(String baseDir, String fileName) throws IOException
444         {
445             base = ConfigurationUtils.locate(fs, baseDir, fileName);
446             catalogCwd = base;
447             default_override = catalogManager.getPreferPublic();
448             catalogManager.debug.message(DEBUG_NORMAL, "Parse catalog: " + fileName);
449 
450             boolean parsed = false;
451 
452             for (int count = 0; !parsed && count < readerArr.size(); count++)
453             {
454                 CatalogReader reader = (CatalogReader) readerArr.get(count);
455                 InputStream inStream;
456 
457                 try
458                 {
459                     inStream = fs.getInputStream(base);
460                 }
461                 catch (Exception ex)
462                 {
463                     catalogManager.debug.message(DEBUG_NORMAL, "Unable to access " + base
464                         + ex.getMessage());
465                     break;
466                 }
467 
468                 try
469                 {
470                     reader.readCatalog(this, inStream);
471                     parsed = true;
472                 }
473                 catch (CatalogException ce)
474                 {
475                     catalogManager.debug.message(DEBUG_NORMAL, "Parse failed for " + fileName
476                             + ce.getMessage());
477                     if (ce.getExceptionType() == CatalogException.PARSE_FAILED)
478                     {
479                         break;
480                     }
481                     else
482                     {
483                         // try again!
484                         continue;
485                     }
486                 }
487                 finally
488                 {
489                     try
490                     {
491                         inStream.close();
492                     }
493                     catch (IOException ioe)
494                     {
495                         // Ignore the exception.
496                         inStream = null;
497                     }
498                 }
499             }
500 
501             if (parsed)
502             {
503                 parsePendingCatalogs();
504             }
505         }
506 
507         /**
508          * Perform character normalization on a URI reference.
509          *
510          * @param uriref The URI reference
511          * @return The normalized URI reference.
512          */
513         @Override
514         protected String normalizeURI(String uriref)
515         {
516             StrSubstitutor substitutor = ((CatalogManager) catalogManager).getStrSubstitutor();
517             String resolved = substitutor != null ? substitutor.replace(uriref) : uriref;
518             return super.normalizeURI(resolved);
519         }
520     }
521 }