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