View Javadoc
1   /*
2    * $Id$
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  package org.apache.struts.config;
22  
23  import java.lang.reflect.Array;
24  import java.lang.reflect.InvocationTargetException;
25  
26  import org.apache.commons.beanutils.ConvertUtils;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  /**
31   * <p>A JavaBean representing the configuration information of a
32   * <code>&lt;form-property&gt;</code> element in a Struts configuration
33   * file.<p>
34   *
35   * @version $Rev$ $Date: 2005-11-12 11:52:08 -0500 (Sat, 12 Nov 2005)$
36   * @since Struts 1.1
37   */
38  public class FormPropertyConfig extends BaseConfig {
39      private static final long serialVersionUID = 8436264202472421426L;
40  
41      /**
42       * The {@code Log} instance for this class.
43       */
44      private transient final Logger log =
45          LoggerFactory.getLogger(FormPropertyConfig.class);
46  
47      // ----------------------------------------------------- Instance Variables
48      // ------------------------------------------------------------- Properties
49  
50      /**
51       * String representation of the initial value for this property.
52       */
53      protected String initial = null;
54  
55      /**
56       * The JavaBean property name of the property described by this element.
57       */
58      protected String name = null;
59  
60      /**
61       * <p>The conditions under which the property described by this element
62       * should be reset to its <code>initial</code> value when the form's
63       * <code>reset</code> method is called.</p> <p>This may be set to true (to
64       * always reset the property) or a comma-separated list of HTTP request
65       * methods.</p>
66       *
67       * @since Struts 1.3
68       */
69      protected String reset = null;
70  
71      /**
72       * <p>The size of the array to be created if this property is an array
73       * type and there is no specified <code>initial</code> value.  This value
74       * must be non-negative.</p>
75       *
76       * @since Struts 1.1
77       */
78      protected int size = 0;
79  
80      /**
81       * The fully qualified Java class name of the implementation class of this
82       * bean property, optionally followed by <code>[]</code> to indicate that
83       * the property is indexed.
84       */
85      protected String type = null;
86  
87      // ----------------------------------------------------------- Constructors
88  
89      /**
90       * Standard no-arguments constructor for dynamic instantiation.
91       */
92      public FormPropertyConfig() {
93          super();
94      }
95  
96      /**
97       * Constructor that preconfigures the relevant properties.
98       *
99       * @param name    Name of this property
100      * @param type    Fully qualified class name of this property
101      * @param initial Initial value of this property (if any)
102      */
103     public FormPropertyConfig(String name, String type, String initial) {
104         this(name, type, initial, 0);
105     }
106 
107     /**
108      * Constructor that preconfigures the relevant properties.
109      *
110      * @param name    Name of this property
111      * @param type    Fully qualified class name of this property
112      * @param initial Initial value of this property (if any)
113      * @param reset   The conditions under which this property will be reset
114      *                to its initial value.
115      */
116     public FormPropertyConfig(String name, String type, String initial,
117         String reset) {
118         this(name, type, initial, reset, 0);
119     }
120 
121     /**
122      * Constructor that preconfigures the relevant properties.
123      *
124      * @param name    Name of this property
125      * @param type    Fully qualified class name of this property
126      * @param initial Initial value of this property (if any)
127      * @param size    Size of the array to be created if this property is an
128      *                array with no defined initial value
129      */
130     public FormPropertyConfig(String name, String type, String initial, int size) {
131         this(name, type, initial, null, size);
132     }
133 
134     /**
135      * Constructor that preconfigures the relevant properties.
136      *
137      * @param name    Name of this property
138      * @param type    Fully qualified class name of this property
139      * @param initial Initial value of this property (if any)
140      * @param size    Size of the array to be created if this property is an
141      *                array with no defined initial value
142      * @param reset   The conditions under which this property will be reset
143      *                to its initial value.
144      */
145     public FormPropertyConfig(String name, String type, String initial,
146         String reset, int size) {
147         super();
148         setName(name);
149         setType(type);
150         setInitial(initial);
151         setReset(reset);
152         setSize(size);
153     }
154 
155     public String getInitial() {
156         return (this.initial);
157     }
158 
159     public void setInitial(String initial) {
160         if (configured) {
161             throw new IllegalStateException("Configuration is frozen");
162         }
163 
164         this.initial = initial;
165     }
166 
167     public String getName() {
168         return (this.name);
169     }
170 
171     public void setName(String name) {
172         if (configured) {
173             throw new IllegalStateException("Configuration is frozen");
174         }
175 
176         this.name = name;
177     }
178 
179     public String getReset() {
180         return (this.reset);
181     }
182 
183     public void setReset(String reset) {
184         if (configured) {
185             throw new IllegalStateException("Configuration is frozen");
186         }
187 
188         this.reset = reset;
189     }
190 
191     public int getSize() {
192         return (this.size);
193     }
194 
195     public void setSize(int size) {
196         if (configured) {
197             throw new IllegalStateException("Configuration is frozen");
198         }
199 
200         if (size < 0) {
201             throw new IllegalArgumentException("size < 0");
202         }
203 
204         this.size = size;
205     }
206 
207     public String getType() {
208         return (this.type);
209     }
210 
211     public void setType(String type) {
212         if (configured) {
213             throw new IllegalStateException("Configuration is frozen");
214         }
215 
216         this.type = type;
217     }
218 
219     /**
220      * Return a Class corresponds to the value specified for the
221      * <code>type</code> property, taking into account the trailing "[]" for
222      * arrays (as well as the ability to specify primitive Java types).
223      */
224     public Class<?> getTypeClass() {
225         // Identify the base class (in case an array was specified)
226         String baseType = getType();
227         boolean indexed = false;
228 
229         if (baseType.endsWith("[]")) {
230             baseType = baseType.substring(0, baseType.length() - 2);
231             indexed = true;
232         }
233 
234         // Construct an appropriate Class instance for the base class
235         Class<?> baseClass = null;
236 
237         if ("boolean".equals(baseType)) {
238             baseClass = Boolean.TYPE;
239         } else if ("byte".equals(baseType)) {
240             baseClass = Byte.TYPE;
241         } else if ("char".equals(baseType)) {
242             baseClass = Character.TYPE;
243         } else if ("double".equals(baseType)) {
244             baseClass = Double.TYPE;
245         } else if ("float".equals(baseType)) {
246             baseClass = Float.TYPE;
247         } else if ("int".equals(baseType)) {
248             baseClass = Integer.TYPE;
249         } else if ("long".equals(baseType)) {
250             baseClass = Long.TYPE;
251         } else if ("short".equals(baseType)) {
252             baseClass = Short.TYPE;
253         } else {
254             ClassLoader classLoader =
255                 Thread.currentThread().getContextClassLoader();
256 
257             if (classLoader == null) {
258                 classLoader = this.getClass().getClassLoader();
259             }
260 
261             try {
262                 baseClass = classLoader.loadClass(baseType);
263             } catch (ClassNotFoundException ex) {
264                 log.error("Class '{}' not found for property '{}'",
265                     baseType, name);
266                 baseClass = null;
267             }
268         }
269 
270         // Return the base class or an array appropriately
271         if (indexed) {
272             return (Array.newInstance(baseClass, 0).getClass());
273         } else {
274             return (baseClass);
275         }
276     }
277 
278     // --------------------------------------------------------- Public Methods
279 
280     /**
281      * <p>Return an object representing the initial value of this property.
282      * This is calculated according to the following algorithm:</p>
283      *
284      * <ul>
285      *
286      * <li>If the value you have specified for the <code>type</code> property
287      * represents an array (i.e. it ends with "[]"):
288      *
289      * <ul>
290      *
291      * <li>If you have specified a value for the <code>initial</code>
292      * property, <code>ConvertUtils.convert</code> will be called to convert
293      * it into an instance of the specified array type.</li>
294      *
295      * <li>If you have not specified a value for the <code>initial</code>
296      * property, an array of the length specified by the <code>size</code>
297      * property will be created. Each element of the array will be
298      * instantiated via the zero-args constructor on the specified class (if
299      * any). Otherwise, <code>null</code> will be returned.</li>
300      *
301      * </ul></li>
302      *
303      * <li>If the value you have specified for the <code>type</code> property
304      * does not represent an array:
305      *
306      * <ul>
307      *
308      * <li>If you have specified a value for the <code>initial</code>
309      * property, <code>ConvertUtils.convert</code> will be called to convert
310      * it into an object instance.</li>
311      *
312      * <li>If you have not specified a value for the <code>initial</code>
313      * attribute, Struts will instantiate an instance via the zero-args
314      * constructor on the specified class (if any). Otherwise,
315      * <code>null</code> will be returned.</li>
316      *
317      * </ul></li>
318      *
319      * </ul>
320      */
321     public Object initial() {
322         Object initialValue = null;
323 
324         try {
325             Class<?> clazz = getTypeClass();
326 
327             if (clazz.isArray()) {
328                 if (initial != null) {
329                     initialValue = ConvertUtils.convert(initial, clazz);
330                 } else {
331                     initialValue =
332                         Array.newInstance(clazz.getComponentType(), size);
333 
334                     if (!(clazz.getComponentType().isPrimitive())) {
335                         for (int i = 0; i < size; i++) {
336                             try {
337                                 Array.set(initialValue, i,
338                                     clazz.getComponentType().getDeclaredConstructor().newInstance());
339                             } catch (Throwable t) {
340                                 log.error("Unable to create instance of {} for property={}, "
341                                     + "type={}, initial={}, size={}.",
342                                     clazz.getName(), name, type, initial, size);
343 
344                                 //FIXME: Should we just dump the entire application/module ?
345                             }
346                         }
347                     }
348                 }
349             } else {
350                 if (initial != null) {
351                     initialValue = ConvertUtils.convert(initial, clazz);
352                 } else {
353                     initialValue = clazz.getDeclaredConstructor().newInstance();
354                 }
355             }
356         } catch (Throwable t) {
357             initialValue = null;
358         }
359 
360         return (initialValue);
361     }
362 
363     /**
364      * <p>Inherit values that have not been overridden from the provided
365      * config object.  Subclasses overriding this method should verify that
366      * the given parameter is of a class that contains a property it is trying
367      * to inherit:</p>
368      * <pre>
369      * if (config instanceof MyCustomFormPropertyConfig) {
370      *     MyCustomFormPropertyConfig myConfig =
371      *         (MyCustomFormPropertyConfig) config;
372      *
373      *     if (getMyCustomProp() == null) {
374      *         setMyCustomProp(myConfig.getMyCustomProp());
375      *     }
376      * }
377      * </pre>
378      *
379      * @param config The object that this instance will be inheriting its
380      *               values from.
381      */
382     public void inheritFrom(FormPropertyConfig config)
383         throws IllegalAccessException, InvocationTargetException,
384             InstantiationException, ClassNotFoundException {
385         if (configured) {
386             throw new IllegalStateException("Configuration is frozen");
387         }
388 
389         if (getInitial() == null) {
390             setInitial(config.getInitial());
391         }
392 
393         if (getName() == null) {
394             setName(config.getName());
395         }
396 
397         if (getSize() == 0) {
398             setSize(config.getSize());
399         }
400 
401         if (getType() == null) {
402             setType(config.getType());
403         }
404 
405         inheritProperties(config);
406     }
407 
408     /**
409      * Return a String representation of this object.
410      */
411     public String toString() {
412         StringBuilder sb = new StringBuilder("FormPropertyConfig[");
413 
414         sb.append("name=");
415         sb.append(this.name);
416         sb.append(",type=");
417         sb.append(this.type);
418         sb.append(",initial=");
419         sb.append(this.initial);
420         sb.append(",reset=");
421         sb.append(this.reset);
422         sb.append("]");
423 
424         return (sb.toString());
425     }
426 }