001 /* $Id: CallMethodRule.java 729106 2008-12-23 20:48:09Z rahul $
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one or more
004 * contributor license agreements. See the NOTICE file distributed with
005 * this work for additional information regarding copyright ownership.
006 * The ASF licenses this file to You under the Apache License, Version 2.0
007 * (the "License"); you may not use this file except in compliance with
008 * the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019
020 package org.apache.commons.digester;
021
022
023 import org.apache.commons.beanutils.ConvertUtils;
024 import org.apache.commons.beanutils.MethodUtils;
025 import org.xml.sax.Attributes;
026
027
028 /**
029 * <p>Rule implementation that calls a method on an object on the stack
030 * (normally the top/parent object), passing arguments collected from
031 * subsequent <code>CallParamRule</code> rules or from the body of this
032 * element. </p>
033 *
034 * <p>By using {@link #CallMethodRule(String methodName)}
035 * a method call can be made to a method which accepts no
036 * arguments.</p>
037 *
038 * <p>Incompatible method parameter types are converted
039 * using <code>org.apache.commons.beanutils.ConvertUtils</code>.
040 * </p>
041 *
042 * <p>This rule now uses {@link MethodUtils#invokeMethod} by default.
043 * This increases the kinds of methods successfully and allows primitives
044 * to be matched by passing in wrapper classes.
045 * There are rare cases when {@link MethodUtils#invokeExactMethod}
046 * (the old default) is required.
047 * This method is much stricter in it's reflection.
048 * Setting the <code>UseExactMatch</code> to true reverts to the use of this
049 * method.</p>
050 *
051 * <p>Note that the target method is invoked when the <i>end</i> of
052 * the tag the CallMethodRule fired on is encountered, <i>not</i> when the
053 * last parameter becomes available. This implies that rules which fire on
054 * tags nested within the one associated with the CallMethodRule will
055 * fire before the CallMethodRule invokes the target method. This behaviour is
056 * not configurable. </p>
057 *
058 * <p>Note also that if a CallMethodRule is expecting exactly one parameter
059 * and that parameter is not available (eg CallParamRule is used with an
060 * attribute name but the attribute does not exist) then the method will
061 * not be invoked. If a CallMethodRule is expecting more than one parameter,
062 * then it is always invoked, regardless of whether the parameters were
063 * available or not; missing parameters are converted to the appropriate target
064 * type by calling ConvertUtils.convert. Note that the default ConvertUtils
065 * converters for the String type returns a null when passed a null, meaning
066 * that CallMethodRule will passed null for all String parameters for which
067 * there is no parameter info available from the XML. However parameters of
068 * type Float and Integer will be passed a real object containing a zero value
069 * as that is the output of the default ConvertUtils converters for those
070 * types when passed a null. You can register custom converters to change
071 * this behaviour; see the beautils library documentation for more info.</p>
072 *
073 * <p>Note that when a constructor is used with paramCount=0, indicating that
074 * the body of the element is to be passed to the target method, an empty
075 * element will cause an <i>empty string</i> to be passed to the target method,
076 * not null. And if automatic type conversion is being applied (ie if the
077 * target function takes something other than a string as a parameter) then
078 * the conversion will fail if the converter class does not accept an empty
079 * string as valid input.</p>
080 *
081 * <p>CallMethodRule has a design flaw which can cause it to fail under
082 * certain rule configurations. All CallMethodRule instances share a single
083 * parameter stack, and all CallParamRule instances simply store their data
084 * into the parameter-info structure that is on the top of the stack. This
085 * means that two CallMethodRule instances cannot be associated with the
086 * same pattern without getting scrambled parameter data. This same issue
087 * also applies when a CallMethodRule matches some element X, a different
088 * CallMethodRule matches a child element Y and some of the CallParamRules
089 * associated with the first CallMethodRule match element Y or one of its
090 * child elements. This issue has been present since the very first release
091 * of Digester. Note, however, that this configuration of CallMethodRule
092 * instances is not commonly required.</p>
093 */
094
095 public class CallMethodRule extends Rule {
096
097 // ----------------------------------------------------------- Constructors
098
099 /**
100 * Construct a "call method" rule with the specified method name. The
101 * parameter types (if any) default to java.lang.String.
102 *
103 * @param digester The associated Digester
104 * @param methodName Method name of the parent method to call
105 * @param paramCount The number of parameters to collect, or
106 * zero for a single argument from the body of this element.
107 *
108 *
109 * @deprecated The digester instance is now set in the {@link Digester#addRule} method.
110 * Use {@link #CallMethodRule(String methodName,int paramCount)} instead.
111 */
112 public CallMethodRule(Digester digester, String methodName,
113 int paramCount) {
114
115 this(methodName, paramCount);
116
117 }
118
119
120 /**
121 * Construct a "call method" rule with the specified method name.
122 *
123 * @param digester The associated Digester
124 * @param methodName Method name of the parent method to call
125 * @param paramCount The number of parameters to collect, or
126 * zero for a single argument from the body of ths element
127 * @param paramTypes The Java class names of the arguments
128 * (if you wish to use a primitive type, specify the corresonding
129 * Java wrapper class instead, such as <code>java.lang.Boolean</code>
130 * for a <code>boolean</code> parameter)
131 *
132 * @deprecated The digester instance is now set in the {@link Digester#addRule} method.
133 * Use {@link #CallMethodRule(String methodName,int paramCount, String [] paramTypes)} instead.
134 */
135 public CallMethodRule(Digester digester, String methodName,
136 int paramCount, String paramTypes[]) {
137
138 this(methodName, paramCount, paramTypes);
139
140 }
141
142
143 /**
144 * Construct a "call method" rule with the specified method name.
145 *
146 * @param digester The associated Digester
147 * @param methodName Method name of the parent method to call
148 * @param paramCount The number of parameters to collect, or
149 * zero for a single argument from the body of ths element
150 * @param paramTypes The Java classes that represent the
151 * parameter types of the method arguments
152 * (if you wish to use a primitive type, specify the corresonding
153 * Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
154 * for a <code>boolean</code> parameter)
155 *
156 * @deprecated The digester instance is now set in the {@link Digester#addRule} method.
157 * Use {@link #CallMethodRule(String methodName,int paramCount, Class [] paramTypes)} instead.
158 */
159 public CallMethodRule(Digester digester, String methodName,
160 int paramCount, Class<?> paramTypes[]) {
161
162 this(methodName, paramCount, paramTypes);
163 }
164
165
166 /**
167 * Construct a "call method" rule with the specified method name. The
168 * parameter types (if any) default to java.lang.String.
169 *
170 * @param methodName Method name of the parent method to call
171 * @param paramCount The number of parameters to collect, or
172 * zero for a single argument from the body of this element.
173 */
174 public CallMethodRule(String methodName,
175 int paramCount) {
176 this(0, methodName, paramCount);
177 }
178
179 /**
180 * Construct a "call method" rule with the specified method name. The
181 * parameter types (if any) default to java.lang.String.
182 *
183 * @param targetOffset location of the target object. Positive numbers are
184 * relative to the top of the digester object stack. Negative numbers
185 * are relative to the bottom of the stack. Zero implies the top
186 * object on the stack.
187 * @param methodName Method name of the parent method to call
188 * @param paramCount The number of parameters to collect, or
189 * zero for a single argument from the body of this element.
190 */
191 public CallMethodRule(int targetOffset,
192 String methodName,
193 int paramCount) {
194
195 this.targetOffset = targetOffset;
196 this.methodName = methodName;
197 this.paramCount = paramCount;
198 if (paramCount == 0) {
199 this.paramTypes = new Class[] { String.class };
200 } else {
201 this.paramTypes = new Class[paramCount];
202 for (int i = 0; i < this.paramTypes.length; i++) {
203 this.paramTypes[i] = String.class;
204 }
205 }
206
207 }
208
209 /**
210 * Construct a "call method" rule with the specified method name.
211 * The method should accept no parameters.
212 *
213 * @param methodName Method name of the parent method to call
214 */
215 public CallMethodRule(String methodName) {
216
217 this(0, methodName, 0, (Class[]) null);
218
219 }
220
221
222 /**
223 * Construct a "call method" rule with the specified method name.
224 * The method should accept no parameters.
225 *
226 * @param targetOffset location of the target object. Positive numbers are
227 * relative to the top of the digester object stack. Negative numbers
228 * are relative to the bottom of the stack. Zero implies the top
229 * object on the stack.
230 * @param methodName Method name of the parent method to call
231 */
232 public CallMethodRule(int targetOffset, String methodName) {
233
234 this(targetOffset, methodName, 0, (Class[]) null);
235
236 }
237
238
239 /**
240 * Construct a "call method" rule with the specified method name and
241 * parameter types. If <code>paramCount</code> is set to zero the rule
242 * will use the body of this element as the single argument of the
243 * method, unless <code>paramTypes</code> is null or empty, in this
244 * case the rule will call the specified method with no arguments.
245 *
246 * @param methodName Method name of the parent method to call
247 * @param paramCount The number of parameters to collect, or
248 * zero for a single argument from the body of ths element
249 * @param paramTypes The Java class names of the arguments
250 * (if you wish to use a primitive type, specify the corresonding
251 * Java wrapper class instead, such as <code>java.lang.Boolean</code>
252 * for a <code>boolean</code> parameter)
253 */
254 public CallMethodRule(
255 String methodName,
256 int paramCount,
257 String paramTypes[]) {
258 this(0, methodName, paramCount, paramTypes);
259 }
260
261 /**
262 * Construct a "call method" rule with the specified method name and
263 * parameter types. If <code>paramCount</code> is set to zero the rule
264 * will use the body of this element as the single argument of the
265 * method, unless <code>paramTypes</code> is null or empty, in this
266 * case the rule will call the specified method with no arguments.
267 *
268 * @param targetOffset location of the target object. Positive numbers are
269 * relative to the top of the digester object stack. Negative numbers
270 * are relative to the bottom of the stack. Zero implies the top
271 * object on the stack.
272 * @param methodName Method name of the parent method to call
273 * @param paramCount The number of parameters to collect, or
274 * zero for a single argument from the body of ths element
275 * @param paramTypes The Java class names of the arguments
276 * (if you wish to use a primitive type, specify the corresonding
277 * Java wrapper class instead, such as <code>java.lang.Boolean</code>
278 * for a <code>boolean</code> parameter)
279 */
280 public CallMethodRule( int targetOffset,
281 String methodName,
282 int paramCount,
283 String paramTypes[]) {
284
285 this.targetOffset = targetOffset;
286 this.methodName = methodName;
287 this.paramCount = paramCount;
288 if (paramTypes == null) {
289 this.paramTypes = new Class[paramCount];
290 for (int i = 0; i < this.paramTypes.length; i++) {
291 this.paramTypes[i] = String.class;
292 }
293 } else {
294 // copy the parameter class names into an array
295 // the classes will be loaded when the digester is set
296 this.paramClassNames = new String[paramTypes.length];
297 for (int i = 0; i < this.paramClassNames.length; i++) {
298 this.paramClassNames[i] = paramTypes[i];
299 }
300 }
301
302 }
303
304
305 /**
306 * Construct a "call method" rule with the specified method name and
307 * parameter types. If <code>paramCount</code> is set to zero the rule
308 * will use the body of this element as the single argument of the
309 * method, unless <code>paramTypes</code> is null or empty, in this
310 * case the rule will call the specified method with no arguments.
311 *
312 * @param methodName Method name of the parent method to call
313 * @param paramCount The number of parameters to collect, or
314 * zero for a single argument from the body of ths element
315 * @param paramTypes The Java classes that represent the
316 * parameter types of the method arguments
317 * (if you wish to use a primitive type, specify the corresonding
318 * Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
319 * for a <code>boolean</code> parameter)
320 */
321 public CallMethodRule(
322 String methodName,
323 int paramCount,
324 Class<?> paramTypes[]) {
325 this(0, methodName, paramCount, paramTypes);
326 }
327
328 /**
329 * Construct a "call method" rule with the specified method name and
330 * parameter types. If <code>paramCount</code> is set to zero the rule
331 * will use the body of this element as the single argument of the
332 * method, unless <code>paramTypes</code> is null or empty, in this
333 * case the rule will call the specified method with no arguments.
334 *
335 * @param targetOffset location of the target object. Positive numbers are
336 * relative to the top of the digester object stack. Negative numbers
337 * are relative to the bottom of the stack. Zero implies the top
338 * object on the stack.
339 * @param methodName Method name of the parent method to call
340 * @param paramCount The number of parameters to collect, or
341 * zero for a single argument from the body of ths element
342 * @param paramTypes The Java classes that represent the
343 * parameter types of the method arguments
344 * (if you wish to use a primitive type, specify the corresonding
345 * Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
346 * for a <code>boolean</code> parameter)
347 */
348 public CallMethodRule( int targetOffset,
349 String methodName,
350 int paramCount,
351 Class<?> paramTypes[]) {
352
353 this.targetOffset = targetOffset;
354 this.methodName = methodName;
355 this.paramCount = paramCount;
356 if (paramTypes == null) {
357 this.paramTypes = new Class[paramCount];
358 for (int i = 0; i < this.paramTypes.length; i++) {
359 this.paramTypes[i] = String.class;
360 }
361 } else {
362 this.paramTypes = new Class[paramTypes.length];
363 for (int i = 0; i < this.paramTypes.length; i++) {
364 this.paramTypes[i] = paramTypes[i];
365 }
366 }
367
368 }
369
370
371 // ----------------------------------------------------- Instance Variables
372
373
374 /**
375 * The body text collected from this element.
376 */
377 protected String bodyText = null;
378
379
380 /**
381 * location of the target object for the call, relative to the
382 * top of the digester object stack. The default value of zero
383 * means the target object is the one on top of the stack.
384 */
385 protected int targetOffset = 0;
386
387 /**
388 * The method name to call on the parent object.
389 */
390 protected String methodName = null;
391
392
393 /**
394 * The number of parameters to collect from <code>MethodParam</code> rules.
395 * If this value is zero, a single parameter will be collected from the
396 * body of this element.
397 */
398 protected int paramCount = 0;
399
400
401 /**
402 * The parameter types of the parameters to be collected.
403 */
404 protected Class<?> paramTypes[] = null;
405
406 /**
407 * The names of the classes of the parameters to be collected.
408 * This attribute allows creation of the classes to be postponed until the digester is set.
409 */
410 private String paramClassNames[] = null;
411
412 /**
413 * Should <code>MethodUtils.invokeExactMethod</code> be used for reflection.
414 */
415 protected boolean useExactMatch = false;
416
417 // --------------------------------------------------------- Public Methods
418
419 /**
420 * Should <code>MethodUtils.invokeExactMethod</code>
421 * be used for the reflection.
422 */
423 public boolean getUseExactMatch() {
424 return useExactMatch;
425 }
426
427 /**
428 * Set whether <code>MethodUtils.invokeExactMethod</code>
429 * should be used for the reflection.
430 */
431 public void setUseExactMatch(boolean useExactMatch)
432 {
433 this.useExactMatch = useExactMatch;
434 }
435
436 /**
437 * Set the associated digester.
438 * If needed, this class loads the parameter classes from their names.
439 */
440 public void setDigester(Digester digester)
441 {
442 // call superclass
443 super.setDigester(digester);
444 // if necessary, load parameter classes
445 if (this.paramClassNames != null) {
446 this.paramTypes = new Class[paramClassNames.length];
447 for (int i = 0; i < this.paramClassNames.length; i++) {
448 try {
449 this.paramTypes[i] =
450 digester.getClassLoader().loadClass(this.paramClassNames[i]);
451 } catch (ClassNotFoundException e) {
452 // use the digester log
453 digester.getLogger().error("(CallMethodRule) Cannot load class " + this.paramClassNames[i], e);
454 this.paramTypes[i] = null; // Will cause NPE later
455 }
456 }
457 }
458 }
459
460 /**
461 * Process the start of this element.
462 *
463 * @param attributes The attribute list for this element
464 */
465 public void begin(Attributes attributes) throws Exception {
466
467 // Push an array to capture the parameter values if necessary
468 if (paramCount > 0) {
469 Object parameters[] = new Object[paramCount];
470 for (int i = 0; i < parameters.length; i++) {
471 parameters[i] = null;
472 }
473 digester.pushParams(parameters);
474 }
475
476 }
477
478
479 /**
480 * Process the body text of this element.
481 *
482 * @param bodyText The body text of this element
483 */
484 public void body(String bodyText) throws Exception {
485
486 if (paramCount == 0) {
487 this.bodyText = bodyText.trim();
488 }
489
490 }
491
492
493 /**
494 * Process the end of this element.
495 */
496 public void end() throws Exception {
497
498 // Retrieve or construct the parameter values array
499 Object parameters[] = null;
500 if (paramCount > 0) {
501
502 parameters = (Object[]) digester.popParams();
503
504 if (digester.log.isTraceEnabled()) {
505 for (int i=0,size=parameters.length;i<size;i++) {
506 digester.log.trace("[CallMethodRule](" + i + ")" + parameters[i]) ;
507 }
508 }
509
510 // In the case where the target method takes a single parameter
511 // and that parameter does not exist (the CallParamRule never
512 // executed or the CallParamRule was intended to set the parameter
513 // from an attribute but the attribute wasn't present etc) then
514 // skip the method call.
515 //
516 // This is useful when a class has a "default" value that should
517 // only be overridden if data is present in the XML. I don't
518 // know why this should only apply to methods taking *one*
519 // parameter, but it always has been so we can't change it now.
520 if (paramCount == 1 && parameters[0] == null) {
521 return;
522 }
523
524 } else if (paramTypes != null && paramTypes.length != 0) {
525 // Having paramCount == 0 and paramTypes.length == 1 indicates
526 // that we have the special case where the target method has one
527 // parameter being the body text of the current element.
528
529 // There is no body text included in the source XML file,
530 // so skip the method call
531 if (bodyText == null) {
532 return;
533 }
534
535 parameters = new Object[1];
536 parameters[0] = bodyText;
537 if (paramTypes.length == 0) {
538 paramTypes = new Class[1];
539 paramTypes[0] = String.class;
540 }
541
542 } else {
543 // When paramCount is zero and paramTypes.length is zero it
544 // means that we truly are calling a method with no parameters.
545 // Nothing special needs to be done here.
546 ;
547 }
548
549 // Construct the parameter values array we will need
550 // We only do the conversion if the param value is a String and
551 // the specified paramType is not String.
552 Object paramValues[] = new Object[paramTypes.length];
553 for (int i = 0; i < paramTypes.length; i++) {
554 // convert nulls and convert stringy parameters
555 // for non-stringy param types
556 if(
557 parameters[i] == null ||
558 (parameters[i] instanceof String &&
559 !String.class.isAssignableFrom(paramTypes[i]))) {
560
561 paramValues[i] =
562 ConvertUtils.convert((String) parameters[i], paramTypes[i]);
563 } else {
564 paramValues[i] = parameters[i];
565 }
566 }
567
568 // Determine the target object for the method call
569 Object target;
570 if (targetOffset >= 0) {
571 target = digester.peek(targetOffset);
572 } else {
573 target = digester.peek( digester.getCount() + targetOffset );
574 }
575
576 if (target == null) {
577 StringBuffer sb = new StringBuffer();
578 sb.append("[CallMethodRule]{");
579 sb.append(digester.match);
580 sb.append("} Call target is null (");
581 sb.append("targetOffset=");
582 sb.append(targetOffset);
583 sb.append(",stackdepth=");
584 sb.append(digester.getCount());
585 sb.append(")");
586 throw new org.xml.sax.SAXException(sb.toString());
587 }
588
589 // Invoke the required method on the top object
590 if (digester.log.isDebugEnabled()) {
591 StringBuffer sb = new StringBuffer("[CallMethodRule]{");
592 sb.append(digester.match);
593 sb.append("} Call ");
594 sb.append(target.getClass().getName());
595 sb.append(".");
596 sb.append(methodName);
597 sb.append("(");
598 for (int i = 0; i < paramValues.length; i++) {
599 if (i > 0) {
600 sb.append(",");
601 }
602 if (paramValues[i] == null) {
603 sb.append("null");
604 } else {
605 sb.append(paramValues[i].toString());
606 }
607 sb.append("/");
608 if (paramTypes[i] == null) {
609 sb.append("null");
610 } else {
611 sb.append(paramTypes[i].getName());
612 }
613 }
614 sb.append(")");
615 digester.log.debug(sb.toString());
616 }
617
618 Object result = null;
619 if (useExactMatch) {
620 // invoke using exact match
621 result = MethodUtils.invokeExactMethod(target, methodName,
622 paramValues, paramTypes);
623
624 } else {
625 // invoke using fuzzier match
626 result = MethodUtils.invokeMethod(target, methodName,
627 paramValues, paramTypes);
628 }
629
630 processMethodCallResult(result);
631 }
632
633
634 /**
635 * Clean up after parsing is complete.
636 */
637 public void finish() throws Exception {
638
639 bodyText = null;
640
641 }
642
643 /**
644 * Subclasses may override this method to perform additional processing of the
645 * invoked method's result.
646 *
647 * @param result the Object returned by the method invoked, possibly null
648 */
649 protected void processMethodCallResult(Object result) {
650 // do nothing
651 }
652
653 /**
654 * Render a printable version of this Rule.
655 */
656 public String toString() {
657
658 StringBuffer sb = new StringBuffer("CallMethodRule[");
659 sb.append("methodName=");
660 sb.append(methodName);
661 sb.append(", paramCount=");
662 sb.append(paramCount);
663 sb.append(", paramTypes={");
664 if (paramTypes != null) {
665 for (int i = 0; i < paramTypes.length; i++) {
666 if (i > 0) {
667 sb.append(", ");
668 }
669 sb.append(paramTypes[i].getName());
670 }
671 }
672 sb.append("}");
673 sb.append("]");
674 return (sb.toString());
675
676 }
677
678
679 }