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.lang.reflect.Method;
23  import org.apache.velocity.tools.OldToolInfo;
24  import org.apache.velocity.tools.ToolInfo;
25  import org.apache.velocity.tools.ClassUtils;
26  
27  /**
28   * <p>This class handles configuration info for tools, including their key,
29   * classname, path restriction, and properties.  It also does fairly
30   * aggresive validation and is able to identify if the tool is "old"
31   * (i.e. designed for VelocityTools 1.x).  Once configuration is
32   * complete, a {@link ToolInfo} instance can be created by calling
33   * {@link #createInfo}.</p>
34   * <p>
35   * Most users will not find themselves directly using the API of this class.
36   * </p>
37   *
38   * @author Nathan Bubna
39   * @version $Id: ToolConfiguration.java 511959 2007-02-26 19:24:39Z nbubna $
40   */
41  public class ToolConfiguration extends Configuration
42  {
43      private enum Status {
44          VALID, OLD, NONE, MISSING, UNSUPPORTED, UNINSTANTIABLE;
45      }
46  
47      private String key;
48      private String classname;
49      private String restrictTo;
50      private Boolean skipSetters;
51      private Status status;
52      private Throwable problem;
53  
54      public void setKey(String key)
55      {
56          this.key = key;
57  
58          // ensure any non-default key is also set as a property
59          if (key != null && !key.equals(getDefaultKey()))
60          {
61              setProperty("key", key);
62          }
63      }
64  
65      public void setClass(Class clazz)
66      {
67          setClassname(clazz.getName());
68      }
69  
70      public void setClassname(String classname)
71      {
72          this.classname = classname;
73          this.status = null;
74      }
75  
76      public void setRestrictTo(String path)
77      {
78          this.restrictTo = path;
79      }
80  
81      public void setSkipSetters(Boolean cfgOnly)
82      {
83          this.skipSetters = cfgOnly;
84      }
85  
86      /**
87       * Returns the key set for this tool. If no key has been explicitly
88       * set, this will return the result of {@link #getDefaultKey()}.
89       */
90      public String getKey()
91      {
92          if (this.key != null)
93          {
94              return this.key;
95          }
96          return getDefaultKey();
97      }
98  
99      /**
100      * Returns the default key value for the set tool class.  First, this
101      * looks for a {@link DefaultKey} annotation on the tool class.  Then,
102      * if there is no default key annotation, the {@link Class#getSimpleName()}
103      * is transformed into the key by removing any 'Tool' suffix and
104      * lowercasing the first character.  This will only return {@code null}
105      * if there is both no key and no classname set for this tool.
106      */
107     public String getDefaultKey()
108     {
109         if (getClassname() != null)
110         {
111             Class clazz = getToolClass();
112             DefaultKey defaultKey =
113                 (DefaultKey)clazz.getAnnotation(DefaultKey.class);
114             if (defaultKey != null)
115             {
116                 return defaultKey.value();
117             }
118             else
119             {
120                 // convert 'FooTool' to 'foo' to mirror most default keys
121                 String name = clazz.getSimpleName();
122                 if (name.endsWith("Tool")) {
123                     int i = name.indexOf("Tool");
124                     name = name.substring(0, i);
125                 }
126                 if (name.length() > 1) {
127                     name = name.substring(0, 1).toLowerCase() +
128                            name.substring(1, name.length());
129                 } else {
130                     name = name.toLowerCase();
131                 }
132                 return name;
133             }
134         }
135         return null;
136     }
137 
138     public String getClassname()
139     {
140         return this.classname;
141     }
142 
143     public Class getToolClass()
144     {
145         try
146         {
147             return ClassUtils.getClass(getClassname());
148         }
149         catch (ClassNotFoundException cnfe)
150         {
151             throw new ConfigurationException(this, cnfe);
152         }
153     }
154 
155     public String[] getInvalidScopes()
156     {
157         InvalidScope invalid =
158             (InvalidScope)getToolClass().getAnnotation(InvalidScope.class);
159         if (invalid != null)
160         {
161             return invalid.value();
162         }
163         else
164         {
165             return new String[] {};
166         }
167     }
168 
169     public String[] getValidScopes()
170     {
171         ValidScope valid =
172             (ValidScope)getToolClass().getAnnotation(ValidScope.class);
173         if (valid != null)
174         {
175             return valid.value();
176         }
177         else
178         {
179             return new String[] {};
180         }
181     }
182 
183     private final Status getStatus()
184     {
185         if (this.status == null)
186         {
187             if (getClassname() == null)
188             {
189                 this.status = Status.NONE;
190             }
191 
192             // check for mere presence of init() or configure()
193             try
194             {
195                 // make sure the classname resolves to a class we have
196                 Class clazz = ClassUtils.getClass(getClassname());
197 
198                 // try hard to ensure we have all necessary supporting classes
199                 digForDependencies(clazz);
200 
201                 // create an instance to make sure we can do that
202                 clazz.newInstance();
203 
204                 // check for an init method
205                 Method init =
206                     clazz.getMethod("init", new Class[]{ Object.class });
207 
208                 // if init is deprecated, then we'll consider it a
209                 // new tool with BC support, not an old tool
210                 Deprecated bc = init.getAnnotation(Deprecated.class);
211                 if (bc == null)
212                 {
213                     this.status = Status.OLD;
214                     this.problem = null;
215                 }
216                 else
217                 {
218                     this.status = Status.VALID;
219                     this.problem = null;
220                 }
221             }
222             catch (NoSuchMethodException nsme)
223             {
224                 // ignore this
225                 this.status = Status.VALID;
226                 this.problem = null;
227             }
228             catch (ClassNotFoundException cnfe)
229             {
230                 this.status = Status.MISSING;
231                 this.problem = cnfe;
232             }
233             catch (NoClassDefFoundError ncdfe)
234             {
235                 this.status = Status.UNSUPPORTED;
236                 this.problem = ncdfe;
237             }
238             catch (Throwable t)
239             {
240                 // for all other problems...
241                 this.status = Status.UNINSTANTIABLE;
242                 this.problem = t;
243             }
244         }
245         return this.status;
246     }
247 
248     private void digForDependencies(Class clazz)
249     {
250         clazz.getDeclaredMethods();
251         clazz.getDeclaredFields();
252 
253         Class superClass = clazz.getSuperclass();
254         if (superClass != null)
255         {
256             digForDependencies(superClass);
257         }
258     }
259 
260     public String getRestrictTo()
261     {
262         return this.restrictTo;
263     }
264 
265     public Boolean getSkipSetters()
266     {
267         return this.skipSetters;
268     }
269 
270     public ToolInfo createInfo()
271     {
272         ToolInfo info = null;
273         Status status = getStatus();
274         switch (status)
275         {
276             case VALID:
277                 info = new ToolInfo(getKey(), getToolClass());
278                 break;
279             case OLD:
280                 info = new OldToolInfo(getKey(), getToolClass());
281                 break;
282             default:
283                 throw new ConfigurationException(this, getError(status));
284         }
285 
286         info.restrictTo(getRestrictTo());
287         if (getSkipSetters() != null)
288         {
289             info.setSkipSetters(getSkipSetters());
290         }
291         // it's ok to use this here, because we know it's the
292         // first time properties have been added to this ToolInfo
293         info.addProperties(getPropertyMap());
294         return info;
295     }
296 
297     private final String getError(Status status)
298     {
299         switch (status)
300         {
301             case NONE:
302                 return "No classname set for: "+this;
303             case MISSING:
304                 return "Couldn't find tool class in the classpath for: "+this+
305                        "("+this.problem+")";
306             case UNSUPPORTED:
307                 return "Couldn't find necessary supporting classes for: "+this+
308                        "("+this.problem+")";
309             case UNINSTANTIABLE:
310                 return "Couldn't instantiate instance of tool for: "+this+
311                        "("+this.problem+")";
312             default:
313                 return "";
314         }
315     }
316 
317     @Override
318     public void addConfiguration(Configuration config)
319     {
320         // copy properties
321         super.addConfiguration(config);
322 
323         // copy values specific to tool configs
324         if (config instanceof ToolConfiguration)
325         {
326             ToolConfiguration that = (ToolConfiguration)config;
327             if (that.getClassname() != null)
328             {
329                 setClassname(that.getClassname());
330             }
331             if (that.getRestrictTo() != null)
332             {
333                 setRestrictTo(that.getRestrictTo());
334             }
335         }
336     }
337 
338     @Override
339     public void validate()
340     {
341         super.validate();
342         
343         // make sure the key is not null
344         if (getKey() == null)
345         {
346             throw new NullKeyException(this);
347         }
348 
349         Status status = getStatus();
350         switch (status)
351         {
352             case VALID:
353             case OLD:
354                 break;
355             default:
356                 throw new ConfigurationException(this, getError(status));
357         }
358     }
359 
360     @Override
361     public int compareTo(Configuration conf)
362     {
363         if (!(conf instanceof ToolConfiguration))
364         {
365             throw new UnsupportedOperationException("ToolConfigurations can only be compared to other ToolConfigurations");
366         }
367 
368         ToolConfiguration tool = (ToolConfiguration)conf;
369         if (getKey() == null && tool.getKey() == null)
370         {
371             return 0;
372         }
373         else if (getKey() == null)
374         {
375             return -1;
376         }
377         else if (tool.getKey() == null)
378         {
379             return 1;
380         }
381         else
382         {
383             return getKey().compareTo(tool.getKey());
384         }
385     }
386 
387     @Override
388     public int hashCode()
389     {
390         if (getKey() == null)
391         {
392             return super.hashCode();
393         }
394         return getKey().hashCode();
395     }
396 
397     @Override
398     public boolean equals(Object obj)
399     {
400         if (getKey() == null || !(obj instanceof ToolConfiguration))
401         {
402             return super.equals(obj);
403         }
404         return getKey().equals(((ToolConfiguration)obj).getKey());
405     }
406 
407     @Override
408     public String toString()
409     {
410         StringBuilder out = new StringBuilder();
411         if (getClassname() == null)
412         {
413             out.append("Tool '");
414             out.append(this.key);
415         }
416         else
417         {
418             switch (getStatus())
419             {
420                 case VALID:
421                     break;
422                 case OLD:
423                     out.append("Old ");
424                     break;
425                 case NONE:
426                 case MISSING:
427                     out.append("Invalid ");
428                     break;
429                 case UNSUPPORTED:
430                     out.append("Unsupported ");
431                     break;
432                 case UNINSTANTIABLE:
433                     out.append("Unusable ");
434                     break;
435                 default:
436                     break;
437             }
438             out.append("Tool '");
439             out.append(getKey());
440         }
441         out.append("' ");
442         out.append("=> ");
443         out.append(getClassname());
444         if (getRestrictTo() != null)
445         {
446             out.append(" only for '");
447             out.append(getRestrictTo());
448             out.append('\'');
449         }
450         out.append(" ");
451         appendProperties(out);
452         return out.toString();
453     }
454 
455 }