View Javadoc

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