1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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><form-bean></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
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
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
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
208 if (getName().equals(ancestorName)) {
209 return true;
210 }
211
212
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
235 FormPropertyConfig[] baseFpcs = config.findFormPropertyConfigs();
236
237 for (int i = 0; i < baseFpcs.length; i++) {
238 FormPropertyConfig baseFpc = baseFpcs[i];
239
240
241 FormPropertyConfig prop =
242 this.findFormPropertyConfig(baseFpc.getName());
243
244 if (prop == null) {
245
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
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
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
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
375
376 Class formClass = form.getClass();
377
378 if (form instanceof BeanValidatorForm) {
379
380
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
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
533
534 if (checkCircularInheritance(moduleConfig)) {
535 throw new IllegalArgumentException(
536 "Circular inheritance detected for form bean " + getName());
537 }
538
539
540 if (!baseConfig.isExtensionProcessed()) {
541 baseConfig.processExtends(moduleConfig);
542 }
543
544
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
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 }