View Javadoc

1   /*
2    * $Id: FormBeanConfig.java 421119 2006-07-12 04:49:11Z wsmoak $
3    *
4    * Copyright 1999-2005 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.struts.config;
19  
20  import org.apache.commons.beanutils.BeanUtils;
21  import org.apache.commons.beanutils.DynaBean;
22  import org.apache.commons.beanutils.MutableDynaClass;
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.apache.struts.action.ActionForm;
26  import org.apache.struts.action.ActionServlet;
27  import org.apache.struts.action.DynaActionForm;
28  import org.apache.struts.action.DynaActionFormClass;
29  import org.apache.struts.chain.commands.util.ClassUtils;
30  import org.apache.struts.chain.contexts.ActionContext;
31  import org.apache.struts.chain.contexts.ServletActionContext;
32  import org.apache.struts.util.RequestUtils;
33  import org.apache.struts.validator.BeanValidatorForm;
34  
35  import java.lang.reflect.InvocationTargetException;
36  
37  import java.util.HashMap;
38  
39  /***
40   * <p>A JavaBean representing the configuration information of a
41   * <code>&lt;form-bean&gt;</code> element in a Struts configuration file.<p>
42   *
43   * @version $Rev: 421119 $ $Date: 2006-01-17 07:26:20 -0500 (Tue, 17 Jan 2006)
44   *          $
45   * @since Struts 1.1
46   */
47  public class FormBeanConfig extends BaseConfig {
48      private static final Log log = LogFactory.getLog(FormBeanConfig.class);
49  
50      // ----------------------------------------------------- Instance Variables
51  
52      /***
53       * The set of FormProperty elements defining dynamic form properties for
54       * this form bean, keyed by property name.
55       */
56      protected HashMap formProperties = new HashMap();
57  
58      /***
59       * <p>The lockable object we can synchronize on when creating
60       * DynaActionFormClass.</p>
61       */
62      protected String lock = "";
63  
64      // ------------------------------------------------------------- Properties
65  
66      /***
67       * The DynaActionFormClass associated with a DynaActionForm.
68       */
69      protected transient DynaActionFormClass dynaActionFormClass;
70  
71      /***
72       * Is the form bean class an instance of DynaActionForm with dynamic
73       * properties?
74       */
75      protected boolean dynamic = false;
76  
77      /***
78       * The name of the FormBeanConfig that this config inherits configuration
79       * information from.
80       */
81      protected String inherit = null;
82  
83      /***
84       * Have the inheritance values for this class been applied?
85       */
86      protected boolean extensionProcessed = false;
87  
88      /***
89       * The unique identifier of this form bean, which is used to reference
90       * this bean in <code>ActionMapping</code> instances as well as for the
91       * name of the request or session attribute under which the corresponding
92       * form bean instance is created or accessed.
93       */
94      protected String name = null;
95  
96      /***
97       * The fully qualified Java class name of the implementation class to be
98       * used or generated.
99       */
100     protected String type = null;
101 
102     /***
103      * Is this DynaClass currently restricted (for DynaBeans with a
104      * MutableDynaClass).
105      */
106     protected boolean restricted = false;
107 
108     /***
109      * <p>Return the DynaActionFormClass associated with a
110      * DynaActionForm.</p>
111      *
112      * @throws IllegalArgumentException if the ActionForm is not dynamic
113      */
114     public DynaActionFormClass getDynaActionFormClass() {
115         if (dynamic == false) {
116             throw new IllegalArgumentException("ActionForm is not dynamic");
117         }
118 
119         synchronized (lock) {
120             if (dynaActionFormClass == null) {
121                 dynaActionFormClass = new DynaActionFormClass(this);
122             }
123         }
124 
125         return dynaActionFormClass;
126     }
127 
128     public boolean getDynamic() {
129         return (this.dynamic);
130     }
131 
132     public String getExtends() {
133         return (this.inherit);
134     }
135 
136     public void setExtends(String extend) {
137         throwIfConfigured();
138         this.inherit = extend;
139     }
140 
141     public boolean isExtensionProcessed() {
142         return extensionProcessed;
143     }
144 
145     public String getName() {
146         return (this.name);
147     }
148 
149     public void setName(String name) {
150         throwIfConfigured();
151         this.name = name;
152     }
153 
154     public String getType() {
155         return (this.type);
156     }
157 
158     public void setType(String type) {
159         throwIfConfigured();
160         this.type = type;
161 
162         Class dynaBeanClass = DynaActionForm.class;
163         Class formBeanClass = formBeanClass();
164 
165         if (formBeanClass != null) {
166             if (dynaBeanClass.isAssignableFrom(formBeanClass)) {
167                 this.dynamic = true;
168             } else {
169                 this.dynamic = false;
170             }
171         } else {
172             this.dynamic = false;
173         }
174     }
175 
176     /***
177      * <p>Indicates whether a MutableDynaClass is currently restricted.</p>
178      * <p>If so, no changes to the existing registration of property names,
179      * data types, readability, or writeability are allowed.</p>
180      */
181     public boolean isRestricted() {
182         return restricted;
183     }
184 
185     /***
186      * <p>Set whether a MutableDynaClass is currently restricted.</p> <p>If
187      * so, no changes to the existing registration of property names, data
188      * types, readability, or writeability are allowed.</p>
189      */
190     public void setRestricted(boolean restricted) {
191         this.restricted = restricted;
192     }
193 
194     // ------------------------------------------------------ Protected Methods
195 
196     /***
197      * <p>Traces the hierarchy of this object to check if any of the ancestors
198      * is extending this instance.</p>
199      *
200      * @param moduleConfig The configuration for the module being configured.
201      * @return true if circular inheritance was detected.
202      */
203     protected boolean checkCircularInheritance(ModuleConfig moduleConfig) {
204         String ancestorName = getExtends();
205 
206         while (ancestorName != null) {
207             // check if we have the same name as an ancestor
208             if (getName().equals(ancestorName)) {
209                 return true;
210             }
211 
212             // get our ancestor's ancestor
213             FormBeanConfig ancestor =
214                 moduleConfig.findFormBeanConfig(ancestorName);
215 
216             ancestorName = ancestor.getExtends();
217         }
218 
219         return false;
220     }
221 
222     /***
223      * <p>Compare the form properties of this bean with that of the given and
224      * copy those that are not present.</p>
225      *
226      * @param config The form bean config to copy properties from.
227      * @see #inheritFrom(FormBeanConfig)
228      */
229     protected void inheritFormProperties(FormBeanConfig config)
230         throws ClassNotFoundException, IllegalAccessException,
231             InstantiationException, InvocationTargetException {
232         throwIfConfigured();
233 
234         // Inherit form property configs
235         FormPropertyConfig[] baseFpcs = config.findFormPropertyConfigs();
236 
237         for (int i = 0; i < baseFpcs.length; i++) {
238             FormPropertyConfig baseFpc = baseFpcs[i];
239 
240             // Do we have this prop?
241             FormPropertyConfig prop =
242                 this.findFormPropertyConfig(baseFpc.getName());
243 
244             if (prop == null) {
245                 // We don't have this, so let's copy it
246                 prop =
247                     (FormPropertyConfig) RequestUtils.applicationInstance(baseFpc.getClass()
248                                                                                  .getName());
249 
250                 BeanUtils.copyProperties(prop, baseFpc);
251                 this.addFormPropertyConfig(prop);
252                 prop.setProperties(baseFpc.copyProperties());
253             }
254         }
255     }
256 
257     // --------------------------------------------------------- Public Methods
258 
259     /***
260      * <p>Create and return an <code>ActionForm</code> instance appropriate to
261      * the information in this <code>FormBeanConfig</code>.</p>
262      *
263      * <p>Although this method is not formally deprecated yet, where possible,
264      * the form which accepts an <code>ActionContext</code> as an argument is
265      * preferred, to help sever direct dependencies on the Servlet API.  As
266      * the ActionContext becomes more familiar in Struts, this method will
267      * almost certainly be deprecated.</p>
268      *
269      * @param servlet The action servlet
270      * @return ActionForm instance
271      * @throws IllegalAccessException if the Class or the appropriate
272      *                                constructor is not accessible
273      * @throws InstantiationException if this Class represents an abstract
274      *                                class, an array class, a primitive type,
275      *                                or void; or if instantiation fails for
276      *                                some other reason
277      */
278     public ActionForm createActionForm(ActionServlet servlet)
279         throws IllegalAccessException, InstantiationException {
280         Object obj = null;
281 
282         // Create a new form bean instance
283         if (getDynamic()) {
284             obj = getDynaActionFormClass().newInstance();
285         } else {
286             obj = formBeanClass().newInstance();
287         }
288 
289         ActionForm form = null;
290 
291         if (obj instanceof ActionForm) {
292             form = (ActionForm) obj;
293         } else {
294             form = new BeanValidatorForm(obj);
295         }
296 
297         form.setServlet(servlet);
298 
299         if (form instanceof DynaBean
300             && ((DynaBean) form).getDynaClass() instanceof MutableDynaClass) {
301             DynaBean dynaBean = (DynaBean) form;
302             MutableDynaClass dynaClass =
303                 (MutableDynaClass) dynaBean.getDynaClass();
304 
305             // Add properties
306             dynaClass.setRestricted(false);
307 
308             FormPropertyConfig[] props = findFormPropertyConfigs();
309 
310             for (int i = 0; i < props.length; i++) {
311                 dynaClass.add(props[i].getName(), props[i].getTypeClass());
312                 dynaBean.set(props[i].getName(), props[i].initial());
313             }
314 
315             dynaClass.setRestricted(isRestricted());
316         }
317 
318         return form;
319     }
320 
321     /***
322      * <p>Create and return an <code>ActionForm</code> instance appropriate to
323      * the information in this <code>FormBeanConfig</code>.</p>
324      * <p><b>NOTE:</b> If the given <code>ActionContext</code> is not of type
325      * <code>ServletActionContext</code> (or a subclass), then the form which
326      * is returned will have a null <code>servlet</code> property.  Some of
327      * the subclasses of <code>ActionForm</code> included in Struts will later
328      * throw a <code>NullPointerException</code> in this case. </p> <p>TODO:
329      * Find a way to control this direct dependency on the Servlet API.</p>
330      *
331      * @param context The ActionContext.
332      * @return ActionForm instance
333      * @throws IllegalAccessException if the Class or the appropriate
334      *                                constructor is not accessible
335      * @throws InstantiationException if this Class represents an abstract
336      *                                class, an array class, a primitive type,
337      *                                or void; or if instantiation fails for
338      *                                some other reason
339      */
340     public ActionForm createActionForm(ActionContext context)
341         throws IllegalAccessException, InstantiationException {
342         ActionServlet actionServlet = null;
343 
344         if (context instanceof ServletActionContext) {
345             ServletActionContext saContext = (ServletActionContext) context;
346 
347             actionServlet = saContext.getActionServlet();
348         }
349 
350         return createActionForm(actionServlet);
351     }
352 
353     /***
354      * <p>Checks if the given <code>ActionForm</code> instance is suitable for
355      * use as an alternative to calling this <code>FormBeanConfig</code>
356      * instance's <code>createActionForm</code> method.</p>
357      *
358      * @param form an existing form instance that may be reused.
359      * @return true if the given form can be reused as the form for this
360      *         config.
361      */
362     public boolean canReuse(ActionForm form) {
363         if (form != null) {
364             if (this.getDynamic()) {
365                 String className = ((DynaBean) form).getDynaClass().getName();
366 
367                 if (className.equals(this.getName())) {
368                     log.debug("Can reuse existing instance (dynamic)");
369 
370                     return (true);
371                 }
372             } else {
373                 try {
374                     // check if the form's class is compatible with the class
375                     //      we're configured for
376                     Class formClass = form.getClass();
377 
378                     if (form instanceof BeanValidatorForm) {
379                         // what we really want is to compare against the
380                         //  BeanValidatorForm's getInstance()
381                         BeanValidatorForm beanValidatorForm =
382                             (BeanValidatorForm) form;
383 
384                         formClass = beanValidatorForm.getInstance().getClass();
385                     }
386 
387                     Class configClass =
388                         ClassUtils.getApplicationClass(this.getType());
389 
390                     if (configClass.isAssignableFrom(formClass)) {
391                         log.debug("Can reuse existing instance (non-dynamic)");
392 
393                         return (true);
394                     }
395                 } catch (Exception e) {
396                     log.debug("Error testing existing instance for reusability; just create a new instance",
397                         e);
398                 }
399             }
400         }
401 
402         return false;
403     }
404 
405     /***
406      * Add a new <code>FormPropertyConfig</code> instance to the set
407      * associated with this module.
408      *
409      * @param config The new configuration instance to be added
410      * @throws IllegalArgumentException if this property name has already been
411      *                                  defined
412      */
413     public void addFormPropertyConfig(FormPropertyConfig config) {
414         throwIfConfigured();
415 
416         if (formProperties.containsKey(config.getName())) {
417             throw new IllegalArgumentException("Property " + config.getName()
418                 + " already defined");
419         }
420 
421         formProperties.put(config.getName(), config);
422     }
423 
424     /***
425      * Return the form property configuration for the specified property name,
426      * if any; otherwise return <code>null</code>.
427      *
428      * @param name Form property name to find a configuration for
429      */
430     public FormPropertyConfig findFormPropertyConfig(String name) {
431         return ((FormPropertyConfig) formProperties.get(name));
432     }
433 
434     /***
435      * Return the form property configurations for this module.  If there are
436      * none, a zero-length array is returned.
437      */
438     public FormPropertyConfig[] findFormPropertyConfigs() {
439         FormPropertyConfig[] results =
440             new FormPropertyConfig[formProperties.size()];
441 
442         return ((FormPropertyConfig[]) formProperties.values().toArray(results));
443     }
444 
445     /***
446      * Freeze the configuration of this component.
447      */
448     public void freeze() {
449         super.freeze();
450 
451         FormPropertyConfig[] fpconfigs = findFormPropertyConfigs();
452 
453         for (int i = 0; i < fpconfigs.length; i++) {
454             fpconfigs[i].freeze();
455         }
456     }
457 
458     /***
459      * <p>Inherit values that have not been overridden from the provided
460      * config object.  Subclasses overriding this method should verify that
461      * the given parameter is of a class that contains a property it is trying
462      * to inherit:</p>
463      *
464      * <pre>
465      * if (config instanceof MyCustomConfig) {
466      *     MyCustomConfig myConfig =
467      *         (MyCustomConfig) config;
468      *
469      *     if (getMyCustomProp() == null) {
470      *         setMyCustomProp(myConfig.getMyCustomProp());
471      *     }
472      * }
473      * </pre>
474      *
475      * <p>If the given <code>config</code> is extending another object, those
476      * extensions should be resolved before it's used as a parameter to this
477      * method.</p>
478      *
479      * @param config The object that this instance will be inheriting its
480      *               values from.
481      * @see #processExtends(ModuleConfig)
482      */
483     public void inheritFrom(FormBeanConfig config)
484         throws ClassNotFoundException, IllegalAccessException,
485             InstantiationException, InvocationTargetException {
486         throwIfConfigured();
487 
488         // Inherit values that have not been overridden
489         if (getName() == null) {
490             setName(config.getName());
491         }
492 
493         if (!isRestricted()) {
494             setRestricted(config.isRestricted());
495         }
496 
497         if (getType() == null) {
498             setType(config.getType());
499         }
500 
501         inheritFormProperties(config);
502         inheritProperties(config);
503     }
504 
505     /***
506      * <p>Inherit configuration information from the FormBeanConfig that this
507      * instance is extending.  This method verifies that any form bean config
508      * object that it inherits from has also had its processExtends() method
509      * called.</p>
510      *
511      * @param moduleConfig The {@link ModuleConfig} that this bean is from.
512      * @see #inheritFrom(FormBeanConfig)
513      */
514     public void processExtends(ModuleConfig moduleConfig)
515         throws ClassNotFoundException, IllegalAccessException,
516             InstantiationException, InvocationTargetException {
517         if (configured) {
518             throw new IllegalStateException("Configuration is frozen");
519         }
520 
521         String ancestor = getExtends();
522 
523         if ((!extensionProcessed) && (ancestor != null)) {
524             FormBeanConfig baseConfig =
525                 moduleConfig.findFormBeanConfig(ancestor);
526 
527             if (baseConfig == null) {
528                 throw new NullPointerException("Unable to find "
529                     + "form bean '" + ancestor + "' to extend.");
530             }
531 
532             // Check against circule inheritance and make sure the base config's
533             //  own extends have been processed already
534             if (checkCircularInheritance(moduleConfig)) {
535                 throw new IllegalArgumentException(
536                     "Circular inheritance detected for form bean " + getName());
537             }
538 
539             // Make sure the ancestor's own extension has been processed.
540             if (!baseConfig.isExtensionProcessed()) {
541                 baseConfig.processExtends(moduleConfig);
542             }
543 
544             // Copy values from the base config
545             inheritFrom(baseConfig);
546         }
547 
548         extensionProcessed = true;
549     }
550 
551     /***
552      * Remove the specified form property configuration instance.
553      *
554      * @param config FormPropertyConfig instance to be removed
555      */
556     public void removeFormPropertyConfig(FormPropertyConfig config) {
557         if (configured) {
558             throw new IllegalStateException("Configuration is frozen");
559         }
560 
561         formProperties.remove(config.getName());
562     }
563 
564     /***
565      * Return a String representation of this object.
566      */
567     public String toString() {
568         StringBuffer sb = new StringBuffer("FormBeanConfig[");
569 
570         sb.append("name=");
571         sb.append(this.name);
572         sb.append(",type=");
573         sb.append(this.type);
574         sb.append(",extends=");
575         sb.append(this.inherit);
576         sb.append("]");
577 
578         return (sb.toString());
579     }
580 
581     // ------------------------------------------------------ Protected Methods
582 
583     /***
584      * Return the <code>Class</code> instance for the form bean implementation
585      * configured by this <code>FormBeanConfig</code> instance.  This method
586      * uses the same algorithm as <code>RequestUtils.applicationClass()</code>
587      * but is reproduced to avoid a runtime dependence.
588      */
589     protected Class formBeanClass() {
590         ClassLoader classLoader =
591             Thread.currentThread().getContextClassLoader();
592 
593         if (classLoader == null) {
594             classLoader = this.getClass().getClassLoader();
595         }
596 
597         try {
598             return (classLoader.loadClass(getType()));
599         } catch (Exception e) {
600             return (null);
601         }
602     }
603 }