Source code: org/apache/struts/action/DynaActionForm.java
1 /*
2 * $Id: DynaActionForm.java 54929 2004-10-16 16:38:42Z germuska $
3 *
4 * Copyright 2000-2004 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
19 package org.apache.struts.action;
20
21 import java.lang.reflect.Array;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.Map;
26
27 import javax.servlet.ServletRequest;
28 import javax.servlet.http.HttpServletRequest;
29
30 import org.apache.commons.beanutils.ConversionException;
31 import org.apache.commons.beanutils.DynaBean;
32 import org.apache.commons.beanutils.DynaClass;
33 import org.apache.commons.beanutils.DynaProperty;
34 import org.apache.struts.config.FormBeanConfig;
35 import org.apache.struts.config.FormPropertyConfig;
36
37
38 /**
39 * <p>Specialized subclass of <code>ActionForm</code> that allows the creation
40 * of form beans with dynamic sets of properties, without requiring the
41 * developer to create a Java class for each type of form bean.</p>
42 *
43 * <p><strong>USAGE NOTE</strong> - Since Struts 1.1, the
44 * <code>reset</code> method no longer initializes property values to those
45 * specified in <code><form-property></code> elements in the Struts
46 * module configuration file. If you wish to utilize that behavior, the
47 * simplest solution is to subclass <code>DynaActionForm</code> and call
48 * the <code>initialize</code> method inside it.</p>
49 *
50 * @version $Rev: 54929 $ $Date: 2004-10-16 09:38:42 -0700 (Sat, 16 Oct 2004) $
51 * @since Struts 1.1
52 */
53 public class DynaActionForm extends ActionForm implements DynaBean {
54
55
56 // ----------------------------------------------------- Instance Variables
57
58
59 /**
60 * <p>The <code>DynaActionFormClass</code> with which we are associated.
61 * </p>
62 */
63 protected DynaActionFormClass dynaClass = null;
64
65
66 /**
67 * <p>The set of property values for this <code>DynaActionForm</code>,
68 * keyed by property name.</p>
69 */
70 protected HashMap dynaValues = new HashMap();
71
72
73 // ----------------------------------------------------- ActionForm Methods
74
75
76 /**
77 * <p>Initialize all bean properties to their initial values, as specified
78 * in the {@link FormPropertyConfig} elements associated with the
79 * definition of this <code>DynaActionForm</code>.</p>
80 *
81 * @param mapping The mapping used to select this instance
82 */
83 public void initialize(ActionMapping mapping) {
84
85 String name = mapping.getName();
86 if (name == null) {
87 return;
88 }
89 FormBeanConfig config =
90 mapping.getModuleConfig().findFormBeanConfig(name);
91 if (config == null) {
92 return;
93 }
94
95 initialize(config);
96 }
97
98 public void initialize(FormBeanConfig config) {
99
100 FormPropertyConfig props[] = config.findFormPropertyConfigs();
101 for (int i = 0; i < props.length; i++) {
102 set(props[i].getName(), props[i].initial());
103 }
104
105 }
106
107
108 // :FIXME: Is there any point in retaining these reset methods
109 // since they now simply replicate the superclass behavior?
110
111 /**
112 * <p>Reset bean properties to their default state, as needed.
113 * This method is called before the properties are repopulated by
114 * the controller.</p>
115 *
116 * <p>The default implementation attempts to forward to the HTTP
117 * version of this method.</p>
118 *
119 * @param mapping The mapping used to select this instance
120 * @param request The servlet request we are processing
121 */
122 public void reset(ActionMapping mapping, ServletRequest request) {
123 super.reset(mapping,request);
124 }
125
126
127 /**
128 * <p>Reset bean properties to their default state, as needed. This method is
129 * called before the properties are repopulated by the controller.</p>
130 *
131 * <p>The default implementation (since Struts 1.1) does nothing.
132 * Subclasses may override this method to reset bean properties to
133 * default values, or the <code>initialize</code> method may be used to
134 * initialize property values to those provided in the form property
135 * configuration information (which was the behavior of
136 * this method in some release candidates).</p>
137 *
138 * @param mapping The mapping used to select this instance
139 * @param request The servlet request we are processing
140 */
141 public void reset(ActionMapping mapping, HttpServletRequest request) {
142 super.reset(mapping,request);
143 }
144
145
146 // ------------------------------------------------------- DynaBean Methods
147
148
149 /**
150 * <p>Indicates if the specified mapped property contain a value for the
151 * specified key value.</p>
152 *
153 * @param name Name of the property to check
154 * @param key Name of the key to check
155 *
156 * @exception IllegalArgumentException if there is no property
157 * of the specified name
158 */
159 public boolean contains(String name, String key) {
160
161 Object value = dynaValues.get(name);
162 if (value == null) {
163 throw new NullPointerException
164 ("No mapped value for '" + name + "(" + key + ")'");
165 } else if (value instanceof Map) {
166 return (((Map) value).containsKey(key));
167 } else {
168 throw new IllegalArgumentException
169 ("Non-mapped property for '" + name + "(" + key + ")'");
170 }
171
172 }
173
174
175 /**
176 * <p>Return the value of a simple property with the specified name.</p>
177 *
178 * @param name Name of the property whose value is to be retrieved
179 *
180 * @exception IllegalArgumentException if there is no property
181 * of the specified name
182 * @exception NullPointerException if the type specified for the
183 * property is invalid
184 */
185 public Object get(String name) {
186
187 // Return any non-null value for the specified property
188 Object value = dynaValues.get(name);
189 if (value != null) {
190 return (value);
191 }
192
193 // Return a null value for a non-primitive property
194 Class type = getDynaProperty(name).getType();
195 if (type == null) {
196 throw new NullPointerException
197 ("The type for property " + name + " is invalid");
198 }
199 if (!type.isPrimitive()) {
200 return (value);
201 }
202
203 // Manufacture default values for primitive properties
204 if (type == Boolean.TYPE) {
205 return (Boolean.FALSE);
206 } else if (type == Byte.TYPE) {
207 return (new Byte((byte) 0));
208 } else if (type == Character.TYPE) {
209 return (new Character((char) 0));
210 } else if (type == Double.TYPE) {
211 return (new Double(0.0));
212 } else if (type == Float.TYPE) {
213 return (new Float((float) 0.0));
214 } else if (type == Integer.TYPE) {
215 return (new Integer(0));
216 } else if (type == Long.TYPE) {
217 return (new Long(0));
218 } else if (type == Short.TYPE) {
219 return (new Short((short) 0));
220 } else {
221 return (null);
222 }
223
224 }
225
226
227 /**
228 * <p>Return the value of an indexed property with the specified name.
229 * </p>
230 *
231 * @param name Name of the property whose value is to be retrieved
232 * @param index Index of the value to be retrieved
233 *
234 * @exception IllegalArgumentException if there is no property
235 * of the specified name
236 * @exception IllegalArgumentException if the specified property
237 * exists, but is not indexed
238 * @exception IndexOutOfBoundsException if the specified index
239 * is outside the range of the underlying property
240 * @exception NullPointerException if no array or List has been
241 * initialized for this property
242 */
243 public Object get(String name, int index) {
244
245 Object value = dynaValues.get(name);
246 if (value == null) {
247 throw new NullPointerException
248 ("No indexed value for '" + name + "[" + index + "]'");
249 } else if (value.getClass().isArray()) {
250 return (Array.get(value, index));
251 } else if (value instanceof List) {
252 return ((List) value).get(index);
253 } else {
254 throw new IllegalArgumentException
255 ("Non-indexed property for '" + name + "[" + index + "]'");
256 }
257
258 }
259
260
261 /**
262 * <p>Return the value of a mapped property with the specified name,
263 * or <code>null</code> if there is no value for the specified key.
264 * </p>
265 *
266 * @param name Name of the property whose value is to be retrieved
267 * @param key Key of the value to be retrieved
268 *
269 * @exception IllegalArgumentException if there is no property
270 * of the specified name
271 * @exception IllegalArgumentException if the specified property
272 * exists, but is not mapped
273 */
274 public Object get(String name, String key) {
275
276 Object value = dynaValues.get(name);
277 if (value == null) {
278 throw new NullPointerException
279 ("No mapped value for '" + name + "(" + key + ")'");
280 } else if (value instanceof Map) {
281 return (((Map) value).get(key));
282 } else {
283 throw new IllegalArgumentException
284 ("Non-mapped property for '" + name + "(" + key + ")'");
285 }
286
287 }
288
289
290 /**
291 * <p>Return the value of a <code>String</code> property with the specified
292 * name. This is equivalent to calling
293 * <code>(String) dynaForm.get(name)</code>.</p>
294 *
295 * @param name Name of the property whose value is to be retrieved
296 *
297 * @throws IllegalArgumentException if there is no property
298 * of the specified name
299 * @throws NullPointerException if the type specified for the
300 * property is invalid
301 * @throws ClassCastException if the property is not a String.
302 * @since Struts 1.2
303 */
304 public String getString(String name) {
305
306 return (String) this.get(name);
307
308 }
309
310
311 /**
312 * <p>Return the value of a <code>String[]</code> property with the
313 * specified name. This is equivalent to calling
314 * <code>(String[]) dynaForm.get(name)</code>.</p>
315 *
316 * @param name Name of the property whose value is to be retrieved
317 *
318 * @throws IllegalArgumentException if there is no property
319 * of the specified name
320 * @throws NullPointerException if the type specified for the
321 * property is invalid
322 * @throws ClassCastException if the property is not a String[].
323 * @since Struts 1.2
324 */
325 public String[] getStrings(String name) {
326
327 return (String[]) this.get(name);
328
329 }
330
331
332 /**
333 * <p>Return the <code>DynaClass</code> instance that describes the set
334 * of properties available for this <code>DynaBean</code>.</p>
335 */
336 public DynaClass getDynaClass() {
337
338 return (this.dynaClass);
339
340 }
341
342
343 /**
344 * <p>Returns the <code>Map</code> containing the property values. This is
345 * done mostly to facilitate accessing the <code>DynaActionForm</code>
346 * through JavaBeans accessors, in order to use the JavaServer Pages
347 * Standard Tag Library (JSTL).</p>
348 *
349 * <p>For instance, the normal JSTL EL syntax for accessing an
350 * <code>ActionForm</code> would be something like this:
351 * <pre>
352 * ${formbean.prop}</pre>
353 * The JSTL EL syntax for accessing a <code>DynaActionForm</code> looks
354 * something like this (because of the presence of this
355 * <code>getMap()</code> method):
356 * <pre>
357 * ${dynabean.map.prop}</pre>
358 * </p>
359 */
360 public Map getMap() {
361
362 return (dynaValues);
363
364 }
365
366
367 /**
368 * <p>Remove any existing value for the specified key on the
369 * specified mapped property.</p>
370 *
371 * @param name Name of the property for which a value is to
372 * be removed
373 * @param key Key of the value to be removed
374 *
375 * @exception IllegalArgumentException if there is no property
376 * of the specified name
377 */
378 public void remove(String name, String key) {
379
380 Object value = dynaValues.get(name);
381 if (value == null) {
382 throw new NullPointerException
383 ("No mapped value for '" + name + "(" + key + ")'");
384 } else if (value instanceof Map) {
385 ((Map) value).remove(key);
386 } else {
387 throw new IllegalArgumentException
388 ("Non-mapped property for '" + name + "(" + key + ")'");
389 }
390
391 }
392
393
394 /**
395 * <p>Set the value of a simple property with the specified name.</p>
396 *
397 * @param name Name of the property whose value is to be set
398 * @param value Value to which this property is to be set
399 *
400 * @exception ConversionException if the specified value cannot be
401 * converted to the type required for this property
402 * @exception IllegalArgumentException if there is no property
403 * of the specified name
404 * @exception NullPointerException if the type specified for the
405 * property is invalid
406 * @exception NullPointerException if an attempt is made to set a
407 * primitive property to null
408 */
409 public void set(String name, Object value) {
410
411 DynaProperty descriptor = getDynaProperty(name);
412 if (descriptor.getType() == null) {
413 throw new NullPointerException
414 ("The type for property " + name + " is invalid");
415 }
416 if (value == null) {
417 if (descriptor.getType().isPrimitive()) {
418 throw new NullPointerException
419 ("Primitive value for '" + name + "'");
420 }
421 } else if (!isDynaAssignable(descriptor.getType(), value.getClass())) {
422 throw new ConversionException
423 ("Cannot assign value of type '" +
424 value.getClass().getName() +
425 "' to property '" + name + "' of type '" +
426 descriptor.getType().getName() + "'");
427 }
428 dynaValues.put(name, value);
429
430 }
431
432
433 /**
434 * <p>Set the value of an indexed property with the specified name.</p>
435 *
436 * @param name Name of the property whose value is to be set
437 * @param index Index of the property to be set
438 * @param value Value to which this property is to be set
439 *
440 * @exception ConversionException if the specified value cannot be
441 * converted to the type required for this property
442 * @exception IllegalArgumentException if there is no property
443 * of the specified name
444 * @exception IllegalArgumentException if the specified property
445 * exists, but is not indexed
446 * @exception IndexOutOfBoundsException if the specified index
447 * is outside the range of the underlying property
448 */
449 public void set(String name, int index, Object value) {
450
451 Object prop = dynaValues.get(name);
452 if (prop == null) {
453 throw new NullPointerException
454 ("No indexed value for '" + name + "[" + index + "]'");
455 } else if (prop.getClass().isArray()) {
456 Array.set(prop, index, value);
457 } else if (prop instanceof List) {
458 try {
459 ((List) prop).set(index, value);
460 } catch (ClassCastException e) {
461 throw new ConversionException(e.getMessage());
462 }
463 } else {
464 throw new IllegalArgumentException
465 ("Non-indexed property for '" + name + "[" + index + "]'");
466 }
467
468 }
469
470
471 /**
472 * <p>Set the value of a mapped property with the specified name.</p>
473 *
474 * @param name Name of the property whose value is to be set
475 * @param key Key of the property to be set
476 * @param value Value to which this property is to be set
477 *
478 * @exception ConversionException if the specified value cannot be
479 * converted to the type required for this property
480 * @exception IllegalArgumentException if there is no property
481 * of the specified name
482 * @exception IllegalArgumentException if the specified property
483 * exists, but is not mapped
484 */
485 public void set(String name, String key, Object value) {
486
487 Object prop = dynaValues.get(name);
488 if (prop == null) {
489 throw new NullPointerException
490 ("No mapped value for '" + name + "(" + key + ")'");
491 } else if (prop instanceof Map) {
492 ((Map) prop).put(key, value);
493 } else {
494 throw new IllegalArgumentException
495 ("Non-mapped property for '" + name + "(" + key + ")'");
496 }
497
498 }
499
500
501 // --------------------------------------------------------- Public Methods
502
503
504 /**
505 * <p>Render a String representation of this object.</p>
506 */
507 public String toString() {
508
509 StringBuffer sb = new StringBuffer("DynaActionForm[dynaClass=");
510 sb.append(getDynaClass().getName());
511 DynaProperty props[] = getDynaClass().getDynaProperties();
512 if (props == null) {
513 props = new DynaProperty[0];
514 }
515 for (int i = 0; i < props.length; i++) {
516 sb.append(',');
517 sb.append(props[i].getName());
518 sb.append('=');
519 Object value = get(props[i].getName());
520 if (value == null) {
521 sb.append("<NULL>");
522 } else if (value.getClass().isArray()) {
523 int n = Array.getLength(value);
524 sb.append("{");
525 for (int j = 0; j < n; j++) {
526 if (j > 0) {
527 sb.append(',');
528 }
529 sb.append(Array.get(value, j));
530 }
531 sb.append("}");
532 } else if (value instanceof List) {
533 int n = ((List) value).size();
534 sb.append("{");
535 for (int j = 0; j < n; j++) {
536 if (j > 0) {
537 sb.append(',');
538 }
539 sb.append(((List) value).get(j));
540 }
541 sb.append("}");
542 } else if (value instanceof Map) {
543 int n = 0;
544 Iterator keys = ((Map) value).keySet().iterator();
545 sb.append("{");
546 while (keys.hasNext()) {
547 if (n > 0) {
548 sb.append(',');
549 }
550 n++;
551 Object key = keys.next();
552 sb.append(key);
553 sb.append('=');
554 sb.append(((Map) value).get(key));
555 }
556 sb.append("}");
557 } else {
558 sb.append(value);
559 }
560 }
561 sb.append("]");
562 return (sb.toString());
563
564 }
565
566
567 // -------------------------------------------------------- Package Methods
568
569
570 /**
571 * <p>Set the <code>DynaActionFormClass</code> instance with which we are
572 * associated.</p>
573 *
574 * @param dynaClass The DynaActionFormClass instance for this bean
575 */
576 void setDynaActionFormClass(DynaActionFormClass dynaClass) {
577
578 this.dynaClass = dynaClass;
579
580 }
581
582
583 // ------------------------------------------------------ Protected Methods
584
585
586 /**
587 * <p>Return the property descriptor for the specified property name.</p>
588 *
589 * @param name Name of the property for which to retrieve the descriptor
590 *
591 * @exception IllegalArgumentException if this is not a valid property
592 * name for our DynaClass
593 */
594 protected DynaProperty getDynaProperty(String name) {
595
596 DynaProperty descriptor = getDynaClass().getDynaProperty(name);
597 if (descriptor == null) {
598 throw new IllegalArgumentException
599 ("Invalid property name '" + name + "'");
600 }
601 return (descriptor);
602
603 }
604
605
606 /**
607 * <p>Indicates if an object of the source class is assignable to the
608 * destination class.</p>
609 *
610 * @param dest Destination class
611 * @param source Source class
612 */
613 protected boolean isDynaAssignable(Class dest, Class source) {
614
615 if (dest.isAssignableFrom(source) ||
616 ((dest == Boolean.TYPE) && (source == Boolean.class)) ||
617 ((dest == Byte.TYPE) && (source == Byte.class)) ||
618 ((dest == Character.TYPE) && (source == Character.class)) ||
619 ((dest == Double.TYPE) && (source == Double.class)) ||
620 ((dest == Float.TYPE) && (source == Float.class)) ||
621 ((dest == Integer.TYPE) && (source == Integer.class)) ||
622 ((dest == Long.TYPE) && (source == Long.class)) ||
623 ((dest == Short.TYPE) && (source == Short.class))) {
624 return (true);
625 } else {
626 return (false);
627 }
628
629 }
630
631
632 }