View Javadoc

1   package org.apache.velocity.tools.config;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.InputStream;
24  import java.io.IOException;
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  import java.lang.reflect.Modifier;
28  import java.net.MalformedURLException;
29  import java.net.URL;
30  import java.util.List;
31  import org.apache.velocity.exception.ResourceNotFoundException;
32  import org.apache.velocity.tools.ClassUtils;
33  import org.apache.velocity.tools.ToolboxFactory;
34  
35  /**
36   * Utility methods for handling tool configurations.
37   *
38   * @author Nathan Bubna
39   * @version $Id: ConfigurationUtils.java 511959 2007-02-26 19:24:39Z nbubna $
40   */
41  public class ConfigurationUtils
42  {
43      public static final String GENERIC_DEFAULTS_PATH =
44          "/org/apache/velocity/tools/generic/tools.xml";
45      public static final String VIEW_DEFAULTS_PATH =
46          "/org/apache/velocity/tools/view/tools.xml";
47      public static final String STRUTS_DEFAULTS_PATH =
48          "/org/apache/velocity/tools/struts/tools.xml";
49  
50      public static final String AUTOLOADED_XML_PATH = "tools.xml";
51      public static final String AUTOLOADED_PROPS_PATH = "tools.properties";
52  
53      public static final String SYSTEM_PROPERTY_KEY =
54          "org.apache.velocity.tools";
55      public static final ConfigurationUtils INSTANCE = new ConfigurationUtils();
56  
57      private ConfigurationUtils() {}
58  
59      public ConfigurationUtils getInstance()
60      {
61          return INSTANCE;
62      }
63  
64      /**
65       * Returns the "default" {@link FactoryConfiguration}.  This includes
66       * all the standard tools developed by this project and available in
67       * the jar being used. In other words, if the velocity-tools-generic-2.x.jar
68       * is being used, then only the generic tools will be included.  If
69       * the velocity-tools-struts-2.x.jar is being used, then all VelocityTools
70       * will be available.  This also means that subclasses in the larger jars
71       * will override their superclasses.  So, if you are using the VelocityStruts
72       * jar, then your $link reference will be a StrutsLinkTool.  If you are using
73       * the VelocityView jar, it will be a standard LinkTool.
74       */
75      public static FactoryConfiguration getDefaultTools()
76      {
77          FileFactoryConfiguration config =
78              new XmlFactoryConfiguration("ConfigurationUtils.getDefaultTools()");
79          config.read(GENERIC_DEFAULTS_PATH);
80  
81          // view tools and struts tools may not be available
82          config.read(VIEW_DEFAULTS_PATH, false);
83          config.read(STRUTS_DEFAULTS_PATH, false);
84  
85          // defaults should *always* be clean!
86          clean(config);
87          return config;
88      }
89  
90      /**
91       * Returns a {@link FactoryConfiguration} including all default
92       * "GenericTools" available and no others.
93       */
94      public static FactoryConfiguration getGenericTools()
95      {
96          FileFactoryConfiguration config =
97              new XmlFactoryConfiguration("ConfigurationUtils.getGenericTools()");
98          config.read(GENERIC_DEFAULTS_PATH);
99  
100         // defaults should *always* be clean!
101         clean(config);
102         return config;
103     }
104 
105     /**
106      * Returns a {@link FactoryConfiguration} including all default
107      * "VelocityView" tools available as well as the default "GenericTools".
108      * @throws {@link ConfigurationException} if a tools.xml is not found
109      *   at the {@link #VIEW_DEFAULTS_PATH}.
110      */
111     public static FactoryConfiguration getVelocityView()
112     {
113         FileFactoryConfiguration config =
114             new XmlFactoryConfiguration("ConfigurationUtils.getVelocityView()");
115         config.read(GENERIC_DEFAULTS_PATH);
116         config.read(VIEW_DEFAULTS_PATH);
117 
118         // defaults should *always* be clean!
119         clean(config);
120         return config;
121     }
122 
123     /**
124      * Returns a {@link FactoryConfiguration} including all default
125      * "VelocityStruts" tools available as well as the default "VelocityView"
126      * tools and "GenericTools".
127      * @throws {@link ConfigurationException} if a tools.xml is not found
128      *   at the {@link #VIEW_DEFAULTS_PATH} or {@link #STRUTS_DEFAULTS_PATH}.
129      */
130     public static FactoryConfiguration getVelocityStruts()
131     {
132         FileFactoryConfiguration config =
133             new XmlFactoryConfiguration("ConfigurationUtils.getVelocityStruts()");
134         config.read(GENERIC_DEFAULTS_PATH);
135         config.read(VIEW_DEFAULTS_PATH);
136         config.read(STRUTS_DEFAULTS_PATH);
137 
138         // defaults should *always* be clean!
139         clean(config);
140         return config;
141     }
142 
143     /**
144      * Returns a {@link FactoryConfiguration} including all 
145      * {@link #getDefaultTools()} as well as any tools that can be
146      * automatically loaded from "tools.xml" or "tools.properties" found
147      * at the root of the classpath or in the current directory.
148      *
149      * @see #getAutoLoaded(boolean includeDefaults)
150      */
151     public static FactoryConfiguration getAutoLoaded()
152     {
153         return getAutoLoaded(true);
154     }
155 
156     /**
157      * Returns a {@link FactoryConfiguration} composed, in order of the
158      * following configurations:
159      * <ul>
160      *   <li>{@link #getDefaultTools()} (only if includeDefaults is {@code true})</li>
161      *   <li>All "tools.xml" configurations found in the classpath root, in the order found</li>
162      *   <li>All "tools.properties" configurations found in the classpath root, in the order found</li>
163      *   <li>The "tools.xml" file in the current directory (if any)</li>
164      *   <li>The "tools.properties" file in the current directory (if any)</li>
165      * </ul>
166      * If the includeDefaults parameter is null and no such files described above
167      * can be found, then the configuration returned by this method will be
168      * empty, but it should never be {@code null}.
169      */
170     public static FactoryConfiguration getAutoLoaded(boolean includeDefaults)
171     {
172         FactoryConfiguration auto;
173         if (includeDefaults)
174         {
175             // start with the available defaults
176             auto = getDefaultTools();
177         }
178         else
179         {
180             // start out blank
181             auto = new FactoryConfiguration("ConfigurationUtils.getAutoLoaded(false)");
182         }
183 
184         //TODO: look for any Tools classes in the root of the classpath
185 
186         // look for all tools.xml in the classpath
187         FactoryConfiguration cpXml = findInClasspath(AUTOLOADED_XML_PATH);
188         if (cpXml != null)
189         {
190             auto.addConfiguration(cpXml);
191         }
192 
193         // look for all tools.properties in the classpath
194         FactoryConfiguration cpProps = findInClasspath(AUTOLOADED_PROPS_PATH);
195         if (cpProps != null)
196         {
197             auto.addConfiguration(cpProps);
198         }
199 
200         // look for tools.xml in the current file system
201         FactoryConfiguration fsXml = findInFileSystem(AUTOLOADED_XML_PATH);
202         if (fsXml != null)
203         {
204             auto.addConfiguration(fsXml);
205         }
206 
207         // look for tools.properties in the file system
208         FactoryConfiguration fsProps = findInFileSystem(AUTOLOADED_PROPS_PATH);
209         if (fsProps != null)
210         {
211             auto.addConfiguration(fsProps);
212         }
213 
214         // return the config we've accumulated
215         return auto;
216     }
217 
218     /**
219      * Returns a {@link FactoryConfiguration} loaded from the path specified
220      * in the "org.apache.velocity.tools" system property (if any).
221      * If no such property has been set {@code null} will be returned.
222      * @throws ResourceNotFoundException if the system property has a value
223      *         but no configuration file was found at the specified location
224      */
225     public static FactoryConfiguration findFromSystemProperty()
226     {
227         String path = System.getProperty(SYSTEM_PROPERTY_KEY);
228         if (path == null || path.length() == 0)
229         {
230             return null;
231         }
232         return load(path);
233     }
234 
235     /**
236      * Returns a new, standard {@link ToolboxFactory} configured
237      * with the results of both {@link #getAutoLoaded()} and
238      * {@link #findFromSystemProperty()}.
239      */
240     public static ToolboxFactory createFactory()
241     {
242         // get the automatically loaded config(s)
243         FactoryConfiguration auto = getAutoLoaded();
244 
245         // include any config specified via system property
246         FactoryConfiguration sys = findFromSystemProperty();
247         if (sys != null)
248         {
249             auto.addConfiguration(sys);
250         }
251 
252         ToolboxFactory factory = new ToolboxFactory();
253         factory.configure(auto);
254         return factory;
255     }
256 
257     /**
258      * Convenience method that automatically creates a new
259      * {@link ConfigurationCleaner} and applies it to the specified
260      * {@link Configuration}.
261      */
262     public static void clean(Configuration config)
263     {
264         // since most config will happen at startup and a cleaner
265         // is not otherwise necessary, don't keep one of these statically
266         ConfigurationCleaner cleaner = new ConfigurationCleaner();
267         cleaner.clean(config);
268     }
269 
270     /**
271      * Returns a {@link FactoryConfiguration} loaded from a configuration file
272      * at the specified path.  If no such file is found at that path, this
273      * will throw a {@link ResourceNotFoundException}.
274      *
275      * @see #find(String path)
276      */
277     public static FactoryConfiguration load(String path)
278     {
279         FactoryConfiguration config = find(path);
280         if (config == null)
281         {
282             throw new ResourceNotFoundException("Could not find configuration at "+path);
283         }
284         return config;
285     }
286 
287     /**
288      * Searches for a configuration file at the specified path and returns
289      * it in the form of a {@link FactoryConfiguration}.  This method will
290      * look for a matching file in both the classpath and the file system.
291      * If perchance a match is found in both, then both are loaded and the
292      * configuration loaded from the file system is given precedence (i.e.
293      * it is added onto the other).  If no match is found in either, then
294      * this will return {@code null}.
295      */
296     public static FactoryConfiguration find(String path)
297     {
298         FactoryConfiguration cp = findInClasspath(path);
299         FactoryConfiguration fs = findInFileSystem(path);
300         if (cp != null)
301         {
302             if (fs != null)
303             {
304                 cp.addConfiguration(fs);
305             }
306             return cp;
307         }
308         else
309         {
310             return fs;
311         }
312     }
313 
314     /**
315      * Searches the file system for a configuration file matching the
316      * specified path.  If found, it will read and return it as a
317      * {@link FactoryConfiguration}.  If not found, this will return
318      * {@code null}.
319      * @throws IllegalStateException if the file exists, but its path could not be converted to a URL for reading.
320      */
321     public static FactoryConfiguration findInFileSystem(String path)
322     {
323         File file = new File(path);
324         if (file.exists())
325         {
326             try
327             {
328                 return read(file.toURL());
329             }
330             catch (MalformedURLException mue)
331             {
332                 throw new IllegalStateException("Could not convert existing file path \""+path+"\" to URL", mue);
333             }
334         }
335         return null;
336     }
337 
338     /**
339      * @see #findInClasspath(String path, Object caller)
340      */
341     public static FactoryConfiguration findInClasspath(String path)
342     {
343         // pretend this was called by a non-static ConfigurationUtils
344         return findInClasspath(path, new ConfigurationUtils());
345     }
346 
347     /**
348      * Searches the classpath for a configuration file matching the
349      * specified path.  If found, it will read and return it as a
350      * {@link FactoryConfiguration}.  If not found, this will return
351      * {@code null}.  If there are multiple matching resources in the
352      * classpath, then they will be added together in the order found
353      * (i.e. the last one will have precedence).
354      * @see ClassUtils#getResources(String path, Object caller)
355      */
356     public static FactoryConfiguration findInClasspath(String path, Object caller)
357     {
358         // find all resources at this path
359         List<URL> found = ClassUtils.getResources(path, caller);
360         if (found.isEmpty())
361         {
362             return null;
363         }
364         else if (found.size() == 1)
365         {
366             // load and return the one config at that URL (if any)
367             return read(found.get(0));
368         }
369         else
370         {
371             // create a base config to combine the others into
372             FactoryConfiguration config =
373                 new FactoryConfiguration("ConfigurationUtils.findInClassPath("+path+","+caller+")");
374             boolean readAConfig = false;
375             for (URL resource : found)
376             {
377                 FactoryConfiguration c = read(resource);
378                 if (c != null)
379                 {
380                     readAConfig = true;
381                     config.addConfiguration(c);
382                 }
383             }
384             // only return a configuration if we really found one
385             if (readAConfig)
386             {
387                 return config;
388             }
389             else
390             {
391                 return null;
392             }
393         }
394     }
395 
396 
397     /**
398      * Returns a {@link FactoryConfiguration} read from a known configuration
399      * file type at the specified {@link URL}.  If the file is missing or unreadable,
400      * this will simply return {@code null} (i.e. if an IOException is thrown).
401      * @throws UnsupportedOperationException if the file type (identified via suffix)
402      *         is neither ".xml" or ".properties"
403      */
404     public static FactoryConfiguration read(URL url)
405     {
406         FileFactoryConfiguration config = null;
407         String path = url.toString();
408         String source = "ConfigurationUtils.read("+url.toString()+")";
409         if (path.endsWith(".xml"))
410         {
411             config = new XmlFactoryConfiguration(source);
412         }
413         else if (path.endsWith(".properties"))
414         {
415             config = new PropertiesFactoryConfiguration(source);
416         }
417         else if (path.endsWith(".class"))
418         {
419             // convert the url to a FQN
420             String fqn = path.substring(0, path.indexOf('.')).replace('/','.');
421             // retrieve a configuration from that class
422             return getFromClass(fqn);
423         }
424         else
425         {
426             String msg = "Unknown configuration file type: " + url.toString() +
427                          "\nOnly .xml and .properties configuration files are supported at this time.";
428             throw new UnsupportedOperationException(msg);
429         }
430 
431         // now, try to read the file
432         InputStream inputStream = null;
433         try
434         {
435             inputStream = url.openStream();
436             config.read(inputStream);
437         }
438         catch (IOException ioe)
439         {
440             return null;
441         }
442         finally
443         {
444             if (inputStream != null)
445             {
446                 try
447                 {
448                     inputStream.close();
449                 }
450                 catch (IOException ioe)
451                 {
452                     throw new RuntimeException("Could not close input stream for "+path, ioe);
453                 }
454             }
455         }
456         return config;
457     }
458 
459     public static FactoryConfiguration getFromClass(String classname)
460     {
461         try
462         {
463             Class configFactory = ClassUtils.getClass(classname);
464             return getFromClass(configFactory);
465         }
466         catch (ClassNotFoundException cnfe)
467         {
468             throw new IllegalArgumentException("Could not find class "+classname, cnfe);
469         }
470     }
471 
472     public static final String CONFIG_FACTORY_METHOD = "getConfiguration";
473     public static FactoryConfiguration getFromClass(Class factory)
474     {
475         //TODO: look for a getConfiguration() method
476         Method getConf = null;
477         try
478         {
479             // check for a public setup(Map) method first
480             getConf = factory.getMethod(CONFIG_FACTORY_METHOD, (Class[])null);
481         }
482         catch (NoSuchMethodException nsme)
483         {
484             throw new IllegalArgumentException("Could not find "+CONFIG_FACTORY_METHOD+" in class "+factory.getName(), nsme);
485         }
486 
487         // get an instance if the method is not static
488         Object instance = null;
489         if (!Modifier.isStatic(getConf.getModifiers()))
490         {
491             try
492             {
493                 instance = factory.newInstance();
494             }
495             catch (Exception e)
496             {
497                 throw new IllegalArgumentException(factory.getName()+" must have usable default constructor or else "+CONFIG_FACTORY_METHOD+" must be declared static", e);
498             }
499         }
500 
501         // invoke the method
502         try
503         {
504             FactoryConfiguration result =
505                 (FactoryConfiguration)getConf.invoke(instance, (Object[])null);
506             if (result == null)
507             {
508                 throw new IllegalArgumentException("Method "+CONFIG_FACTORY_METHOD+" in class "+factory.getName()+" should not return null or void");
509             }
510             else
511             {
512                 return result;
513             }
514         }
515         catch (IllegalAccessException iae)
516         {
517             throw new IllegalArgumentException("Failed to invoke "+CONFIG_FACTORY_METHOD+" in class "+factory.getName(), iae);
518         }
519         catch (IllegalArgumentException iae)
520         {
521             // if this happens, it's more a problem w/this code than the users
522             throw iae;
523         }
524         catch (InvocationTargetException ite)
525         {
526             // if this happens, it's all their issue
527             throw new IllegalArgumentException("There was an exception while executing "+CONFIG_FACTORY_METHOD+" in class "+factory.getName(), ite.getCause());
528         }
529     }
530 
531 }