1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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><form-bean></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
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
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
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
211 if (getName().equals(ancestorName)) {
212 return true;
213 }
214
215
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
238 FormPropertyConfig[] baseFpcs = config.findFormPropertyConfigs();
239
240 for (int i = 0; i < baseFpcs.length; i++) {
241 FormPropertyConfig baseFpc = baseFpcs[i];
242
243
244 FormPropertyConfig prop =
245 this.findFormPropertyConfig(baseFpc.getName());
246
247 if (prop == null) {
248
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
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
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
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
382
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
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
547
548 if (checkCircularInheritance(moduleConfig)) {
549 throw new IllegalArgumentException(
550 "Circular inheritance detected for form bean " + getName());
551 }
552
553
554 if (!baseConfig.isExtensionProcessed()) {
555 baseConfig.processExtends(moduleConfig);
556 }
557
558
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
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 }