View Javadoc

1   package org.apache.velocity.tools.generic;
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.lang.reflect.Field;
23  import java.lang.reflect.Modifier;
24  import java.util.HashMap;
25  import java.util.Map;
26  import org.apache.velocity.runtime.log.Log;
27  import org.apache.velocity.tools.ClassUtils;
28  import org.apache.velocity.tools.config.DefaultKey;
29  
30  /**
31   * <p>
32   * This is a simple tools class to allow easy access to static fields in a class,
33   * such as string constants from within a template.  Velocity will not introspect
34   * for class fields (and won't in the future :), but writing setter/getter methods
35   * to do this is a pain,  so use this if you really have to access fields.
36   *
37   * <p>
38   * <pre>
39   * Example uses in a template:
40   *   ## here we access a constant in a class include in the configuration
41   *     $field.COUNTER_NAME
42   *
43   *   ## here we dynamically lookup a class' fields to find another constant
44   *     $field.in("org.com.SomeClass").ANOTHER_CONSTANT
45   *
46   *   ## here we pass an object instance in (an Integer in this case) and
47   *   ## retrieve a static constant from that instance's class
48   *     $field.in(0).MIN_VALUE
49   *
50   *   ## by default, once we've searched a class' fields, those fields stay
51   *   ## available in the tool (change this by storeDynamicLookups="false")
52   *   ## so here we get another constant from the Integer class
53   *     $field.MAX_VALUE
54   *
55   *
56   * Example tools.xml config:
57   * &lt;tools&gt;
58   *   &lt;toolbox scope="application"&gt;
59   *     &lt;tool class="org.apache.velocity.tools.generic.FieldTool"
60   *              include="org.apache.velocity.runtime.RuntimeConstants,com.org.MyConstants"/&gt;
61   *   &lt;/toolbox&gt;
62   * &lt;/tools&gt;
63   * </pre></p>
64   *
65   * <p>
66   * Right now, this tool only gives access to <code>public static</code> fields.
67   * It seems that anything else is too dangerous.  This is for convenient access
68   * to 'constants'.  If you have fields that aren't <code>static</code>,
69   * handle them by explicitly placing them into the context or writing a getter
70   * method.
71   *
72   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
73   * @author Nathan Bubna
74   * @since VelocityTools 2.0
75   * @version $Id: FieldTool.java 463298 2006-10-12 16:10:32Z henning $
76   */
77  @DefaultKey("field")
78  public class FieldTool extends SafeConfig
79  {
80      /**
81       * The key used for specifying which classes should be inspected
82       * for public static methods to be made available.
83       */
84      public static final String INCLUDE_KEY = "include";
85  
86      /**
87       * The key used for specifying whether or not the tool should store
88       * fields in classes dynamically looked up from within a template.
89       * The default value is true.
90       */
91      public static final String STORE_DYNAMIC_KEY = "storeDynamicLookups";
92  
93      protected Log log;
94      protected HashMap storage = new HashMap();
95      protected boolean storeDynamicLookups = true;
96  
97      protected void configure(ValueParser values)
98      {
99          // see if there's a log in there
100         this.log = (Log)values.getValue("log");
101 
102         // retrieve any classnames to be inspected and inspect them
103         // *before* setting the storeDynamicLookups property!
104         String[] classnames = values.getStrings(INCLUDE_KEY);
105         if (classnames != null)
106         {
107             for (String classname : classnames)
108             {
109                 // make sure we get results for each classname
110                 // since these come from the configuration, it's
111                 // an error if they're invalid
112                 if (in(classname) == null)
113                 {
114                     // shame that ClassNotFoundException is checked...
115                     throw new RuntimeException("Could not find "+classname+" in the classpath");
116                 }
117             }
118         }
119 
120         // find out whether or not we should store dynamic lookups
121         this.storeDynamicLookups =
122             values.getBoolean(STORE_DYNAMIC_KEY, this.storeDynamicLookups);
123     }
124 
125 
126     /**
127      * Returns the value for the specified field name as found
128      * in the stored {@link Map} of field names to values (or placeholders).
129      * Returns {@code null} if there is no matching field.
130      */
131     public Object get(String name)
132     {
133         Object o = storage.get(name);
134         // if it was not a final field, get the current value
135         if (o instanceof MutableField)
136         {
137             return ((MutableField)o).getValue();
138         }
139         // if we have no value and the name looks like a path
140         else if (o == null && name.indexOf('.') > 0)
141         {
142             // treat the name as a full fieldpath
143             try
144             {
145                 return ClassUtils.getFieldValue(name);
146             }
147             catch (Exception e)
148             {
149                 if (log != null)
150                 {
151                     log.debug("Unable to retrieve value of field at "+name, e);
152                 }
153             }
154         }
155         // otherwise, we should have stored the value directly
156         return o;
157     }
158 
159     /**
160      * Returns a {@link FieldToolSub} holding a {@link Map}
161      * of all the public static field names to values (or a placeholder
162      * if the value is not final) for the specified class(name). If the
163      * {@link Class} with the specified name cannot be loaded, this will
164      * return {@code null}, rather than throw an exception.
165      *
166      * @see #in(Class clazz)
167      */
168     public FieldToolSub in(String classname)
169     {
170         try
171         {
172             return in(ClassUtils.getClass(classname));
173         }
174         catch (ClassNotFoundException cnfe)
175         {
176             return null;
177         }
178     }
179 
180     /**
181      * Returns a {@link FieldToolSub} holding a {@link Map}
182      * of all the public static field names to values (or a placeholder
183      * if the value is not final) for the {@link Class} of the
184      * specified Object.
185      * @see #in(Class clazz)
186      */
187     public FieldToolSub in(Object instance)
188     {
189         if (instance == null)
190         {
191             return null;
192         }
193         return in(instance.getClass());
194     }
195 
196     /**
197      * Returns a {@link FieldToolSub} holding a {@link Map}
198      * of all the public static field names to values (or a placeholder
199      * if the value is not final) for the specified {@link Class}.
200      */
201     public FieldToolSub in(Class clazz)
202     {
203         if (clazz == null)
204         {
205             return null;
206         }
207 
208         Map<String,Object> results = inspect(clazz);
209         if (storeDynamicLookups && !results.isEmpty())
210         {
211             storage.putAll(results);
212         }
213         return new FieldToolSub(results);
214     }
215 
216 
217     /**
218      * Looks for all public, static fields in the specified class and
219      * stores their value (if final) or else a {@link MutableField} for
220      * in a {@link Map} under the fields' names.  This will never return
221      * null, only an empty Map if there are no public static fields.
222      */
223     protected Map<String,Object> inspect(Class clazz)
224     {
225         Map<String,Object> results = new HashMap<String,Object>();
226         for(Field field : clazz.getFields())
227         {
228             // ignore anything non-public or non-static
229             int mod = field.getModifiers();
230             if (Modifier.isStatic(mod) && Modifier.isPublic(mod))
231             {
232                 // make it easy to debug key collisions
233                 if (log != null && log.isDebugEnabled() &&
234                     results.containsKey(field.getName()))
235                 {
236                     log.debug("FieldTool: "+field.getName()+
237                               " is being overridden by "+clazz.getName());
238                 }
239                 // if the field is final
240                 if (Modifier.isFinal(mod))
241                 {
242                     // just get the value now
243                     results.put(field.getName(), retrieve(field, clazz, log));
244                 }
245                 else
246                 {
247                     // put a wrapper with easy access
248                     results.put(field.getName(),
249                                 new MutableField(field, clazz, log));
250                 }
251             }
252         }
253         return results;
254     }
255 
256     /**
257      * Retrieves and returns the value of the specified {@link Field}
258      * in the specified {@link Class}.  If {@link Log} is provided, then
259      * access errors will be logged, otherwise this will fail silently
260      * and return {@code null}.
261      */
262     protected static Object retrieve(Field field, Class clazz, Log log)
263     {
264         try
265         {
266             return field.get(clazz);
267         }
268         catch(IllegalAccessException iae)
269         {
270             if (log != null)
271             {
272                 log.warn("IllegalAccessException while trying to access " + field.getName(), iae);
273             }
274             return null;
275         }
276     }
277 
278 
279 
280     /**
281      * Holds a {@link Map} of results for a particular class.
282      * This exists simply to enable the $field.in("class.Name").FOO
283      * syntax, even when storeDynamicLookups is set to false.
284      * NOTE: we can't simply return the results Map when the in()
285      * methods are called, because the Map contains placeholders
286      * for any mutable fields found. We want to put off reading non-final
287      * field values to the last moment, in case their values change.
288      */
289     public static class FieldToolSub
290     {
291         private final Map<String,Object> results;
292 
293         public FieldToolSub(Map<String,Object> results)
294         {
295             if (results == null)
296             {
297                 throw new NullPointerException("Cannot create sub with null field results map");
298             }
299             this.results = results;
300         }
301 
302         public Object get(String name)
303         {
304             Object o = results.get(name);
305             // if it was not a final field, get the current value
306             if (o instanceof MutableField)
307             {
308                 return ((MutableField)o).getValue();
309             }
310             // otherwise, we should have stored the value directly
311             return o;
312         }
313 
314         /**
315          * Return the toString() value of the internal results Map for this sub.
316          */
317         public String toString()
318         {
319             return results.toString();
320         }
321     }
322 
323 
324 
325     /**
326      * Holds a {@link Field} and {@link Class} reference for later
327      * retrieval of the value of a field that is not final and may
328      * change at different lookups.  If a {@link Log} is passed in,
329      * then this will log errors, otherwise it will fail silently.
330      */
331     public static class MutableField
332     {
333         private final Class clazz;
334         private final Field field;
335         private final Log log;
336 
337         public MutableField(Field f, Class c, Log l)
338         {
339             if (f == null || c == null)
340             {
341                 throw new NullPointerException("Both Class and Field must NOT be null");
342             }
343 
344             field = f;
345             clazz = c;
346             log = l;
347         }
348 
349         public Object getValue()
350         {
351             return FieldTool.retrieve(field, clazz, log);
352         }
353     }
354 
355 }