001: /* $Id: CallMethodRule.java 467222 2006-10-24 03:17:11Z markt $
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: package org.apache.tomcat.util.digester;
020:
021: import org.apache.tomcat.util.IntrospectionUtils;
022: import org.xml.sax.Attributes;
023:
024: /**
025: * <p>Rule implementation that calls a method on an object on the stack
026: * (normally the top/parent object), passing arguments collected from
027: * subsequent <code>CallParamRule</code> rules or from the body of this
028: * element. </p>
029: *
030: * <p>By using {@link #CallMethodRule(String methodName)}
031: * a method call can be made to a method which accepts no
032: * arguments.</p>
033: *
034: * <p>Incompatible method parameter types are converted
035: * using <code>org.apache.commons.beanutils.ConvertUtils</code>.
036: * </p>
037: *
038: * <p>This rule now uses
039: * <a href="http://jakarta.apache.org/commons/beanutils/apidocs/org/apache/commons/beanutils/MethodUtils.html">
040: * org.apache.commons.beanutils.MethodUtils#invokeMethod
041: * </a> by default.
042: * This increases the kinds of methods successfully and allows primitives
043: * to be matched by passing in wrapper classes.
044: * There are rare cases when org.apache.commons.beanutils.MethodUtils#invokeExactMethod
045: * (the old default) is required.
046: * This method is much stricter in its reflection.
047: * Setting the <code>UseExactMatch</code> to true reverts to the use of this
048: * method.</p>
049: *
050: * <p>Note that the target method is invoked when the <i>end</i> of
051: * the tag the CallMethodRule fired on is encountered, <i>not</i> when the
052: * last parameter becomes available. This implies that rules which fire on
053: * tags nested within the one associated with the CallMethodRule will
054: * fire before the CallMethodRule invokes the target method. This behaviour is
055: * not configurable. </p>
056: *
057: * <p>Note also that if a CallMethodRule is expecting exactly one parameter
058: * and that parameter is not available (eg CallParamRule is used with an
059: * attribute name but the attribute does not exist) then the method will
060: * not be invoked. If a CallMethodRule is expecting more than one parameter,
061: * then it is always invoked, regardless of whether the parameters were
062: * available or not (missing parameters are passed as null values).</p>
063: */
064:
065: public class CallMethodRule extends Rule {
066:
067: // ----------------------------------------------------------- Constructors
068:
069: /**
070: * Construct a "call method" rule with the specified method name. The
071: * parameter types (if any) default to java.lang.String.
072: *
073: * @param digester The associated Digester
074: * @param methodName Method name of the parent method to call
075: * @param paramCount The number of parameters to collect, or
076: * zero for a single argument from the body of this element.
077: *
078: *
079: * @deprecated The digester instance is now set in the {@link Digester#addRule} method.
080: * Use {@link #CallMethodRule(String methodName,int paramCount)} instead.
081: */
082: public CallMethodRule(Digester digester, String methodName,
083: int paramCount) {
084:
085: this (methodName, paramCount);
086:
087: }
088:
089: /**
090: * Construct a "call method" rule with the specified method name.
091: *
092: * @param digester The associated Digester
093: * @param methodName Method name of the parent method to call
094: * @param paramCount The number of parameters to collect, or
095: * zero for a single argument from the body of ths element
096: * @param paramTypes The Java class names of the arguments
097: * (if you wish to use a primitive type, specify the corresonding
098: * Java wrapper class instead, such as <code>java.lang.Boolean</code>
099: * for a <code>boolean</code> parameter)
100: *
101: * @deprecated The digester instance is now set in the {@link Digester#addRule} method.
102: * Use {@link #CallMethodRule(String methodName,int paramCount, String [] paramTypes)} instead.
103: */
104: public CallMethodRule(Digester digester, String methodName,
105: int paramCount, String paramTypes[]) {
106:
107: this (methodName, paramCount, paramTypes);
108:
109: }
110:
111: /**
112: * Construct a "call method" rule with the specified method name.
113: *
114: * @param digester The associated Digester
115: * @param methodName Method name of the parent method to call
116: * @param paramCount The number of parameters to collect, or
117: * zero for a single argument from the body of ths element
118: * @param paramTypes The Java classes that represent the
119: * parameter types of the method arguments
120: * (if you wish to use a primitive type, specify the corresonding
121: * Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
122: * for a <code>boolean</code> parameter)
123: *
124: * @deprecated The digester instance is now set in the {@link Digester#addRule} method.
125: * Use {@link #CallMethodRule(String methodName,int paramCount, Class [] paramTypes)} instead.
126: */
127: public CallMethodRule(Digester digester, String methodName,
128: int paramCount, Class paramTypes[]) {
129:
130: this (methodName, paramCount, paramTypes);
131: }
132:
133: /**
134: * Construct a "call method" rule with the specified method name. The
135: * parameter types (if any) default to java.lang.String.
136: *
137: * @param methodName Method name of the parent method to call
138: * @param paramCount The number of parameters to collect, or
139: * zero for a single argument from the body of this element.
140: */
141: public CallMethodRule(String methodName, int paramCount) {
142: this (0, methodName, paramCount);
143: }
144:
145: /**
146: * Construct a "call method" rule with the specified method name. The
147: * parameter types (if any) default to java.lang.String.
148: *
149: * @param targetOffset location of the target object. Positive numbers are
150: * relative to the top of the digester object stack. Negative numbers
151: * are relative to the bottom of the stack. Zero implies the top
152: * object on the stack.
153: * @param methodName Method name of the parent method to call
154: * @param paramCount The number of parameters to collect, or
155: * zero for a single argument from the body of this element.
156: */
157: public CallMethodRule(int targetOffset, String methodName,
158: int paramCount) {
159:
160: this .targetOffset = targetOffset;
161: this .methodName = methodName;
162: this .paramCount = paramCount;
163: if (paramCount == 0) {
164: this .paramTypes = new Class[] { String.class };
165: } else {
166: this .paramTypes = new Class[paramCount];
167: for (int i = 0; i < this .paramTypes.length; i++) {
168: this .paramTypes[i] = String.class;
169: }
170: }
171:
172: }
173:
174: /**
175: * Construct a "call method" rule with the specified method name.
176: * The method should accept no parameters.
177: *
178: * @param methodName Method name of the parent method to call
179: */
180: public CallMethodRule(String methodName) {
181:
182: this (0, methodName, 0, (Class[]) null);
183:
184: }
185:
186: /**
187: * Construct a "call method" rule with the specified method name.
188: * The method should accept no parameters.
189: *
190: * @param targetOffset location of the target object. Positive numbers are
191: * relative to the top of the digester object stack. Negative numbers
192: * are relative to the bottom of the stack. Zero implies the top
193: * object on the stack.
194: * @param methodName Method name of the parent method to call
195: */
196: public CallMethodRule(int targetOffset, String methodName) {
197:
198: this (targetOffset, methodName, 0, (Class[]) null);
199:
200: }
201:
202: /**
203: * Construct a "call method" rule with the specified method name and
204: * parameter types. If <code>paramCount</code> is set to zero the rule
205: * will use the body of this element as the single argument of the
206: * method, unless <code>paramTypes</code> is null or empty, in this
207: * case the rule will call the specified method with no arguments.
208: *
209: * @param methodName Method name of the parent method to call
210: * @param paramCount The number of parameters to collect, or
211: * zero for a single argument from the body of ths element
212: * @param paramTypes The Java class names of the arguments
213: * (if you wish to use a primitive type, specify the corresonding
214: * Java wrapper class instead, such as <code>java.lang.Boolean</code>
215: * for a <code>boolean</code> parameter)
216: */
217: public CallMethodRule(String methodName, int paramCount,
218: String paramTypes[]) {
219: this (0, methodName, paramCount, paramTypes);
220: }
221:
222: /**
223: * Construct a "call method" rule with the specified method name and
224: * parameter types. If <code>paramCount</code> is set to zero the rule
225: * will use the body of this element as the single argument of the
226: * method, unless <code>paramTypes</code> is null or empty, in this
227: * case the rule will call the specified method with no arguments.
228: *
229: * @param targetOffset location of the target object. Positive numbers are
230: * relative to the top of the digester object stack. Negative numbers
231: * are relative to the bottom of the stack. Zero implies the top
232: * object on the stack.
233: * @param methodName Method name of the parent method to call
234: * @param paramCount The number of parameters to collect, or
235: * zero for a single argument from the body of ths element
236: * @param paramTypes The Java class names of the arguments
237: * (if you wish to use a primitive type, specify the corresonding
238: * Java wrapper class instead, such as <code>java.lang.Boolean</code>
239: * for a <code>boolean</code> parameter)
240: */
241: public CallMethodRule(int targetOffset, String methodName,
242: int paramCount, String paramTypes[]) {
243:
244: this .targetOffset = targetOffset;
245: this .methodName = methodName;
246: this .paramCount = paramCount;
247: if (paramTypes == null) {
248: this .paramTypes = new Class[paramCount];
249: for (int i = 0; i < this .paramTypes.length; i++) {
250: this .paramTypes[i] = "abc".getClass();
251: }
252: } else {
253: // copy the parameter class names into an array
254: // the classes will be loaded when the digester is set
255: this .paramClassNames = new String[paramTypes.length];
256: for (int i = 0; i < this .paramClassNames.length; i++) {
257: this .paramClassNames[i] = paramTypes[i];
258: }
259: }
260:
261: }
262:
263: /**
264: * Construct a "call method" rule with the specified method name and
265: * parameter types. If <code>paramCount</code> is set to zero the rule
266: * will use the body of this element as the single argument of the
267: * method, unless <code>paramTypes</code> is null or empty, in this
268: * case the rule will call the specified method with no arguments.
269: *
270: * @param methodName Method name of the parent method to call
271: * @param paramCount The number of parameters to collect, or
272: * zero for a single argument from the body of ths element
273: * @param paramTypes The Java classes that represent the
274: * parameter types of the method arguments
275: * (if you wish to use a primitive type, specify the corresonding
276: * Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
277: * for a <code>boolean</code> parameter)
278: */
279: public CallMethodRule(String methodName, int paramCount,
280: Class paramTypes[]) {
281: this (0, methodName, paramCount, paramTypes);
282: }
283:
284: /**
285: * Construct a "call method" rule with the specified method name and
286: * parameter types. If <code>paramCount</code> is set to zero the rule
287: * will use the body of this element as the single argument of the
288: * method, unless <code>paramTypes</code> is null or empty, in this
289: * case the rule will call the specified method with no arguments.
290: *
291: * @param targetOffset location of the target object. Positive numbers are
292: * relative to the top of the digester object stack. Negative numbers
293: * are relative to the bottom of the stack. Zero implies the top
294: * object on the stack.
295: * @param methodName Method name of the parent method to call
296: * @param paramCount The number of parameters to collect, or
297: * zero for a single argument from the body of ths element
298: * @param paramTypes The Java classes that represent the
299: * parameter types of the method arguments
300: * (if you wish to use a primitive type, specify the corresonding
301: * Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
302: * for a <code>boolean</code> parameter)
303: */
304: public CallMethodRule(int targetOffset, String methodName,
305: int paramCount, Class paramTypes[]) {
306:
307: this .targetOffset = targetOffset;
308: this .methodName = methodName;
309: this .paramCount = paramCount;
310: if (paramTypes == null) {
311: this .paramTypes = new Class[paramCount];
312: for (int i = 0; i < this .paramTypes.length; i++) {
313: this .paramTypes[i] = "abc".getClass();
314: }
315: } else {
316: this .paramTypes = new Class[paramTypes.length];
317: for (int i = 0; i < this .paramTypes.length; i++) {
318: this .paramTypes[i] = paramTypes[i];
319: }
320: }
321:
322: }
323:
324: // ----------------------------------------------------- Instance Variables
325:
326: /**
327: * The body text collected from this element.
328: */
329: protected String bodyText = null;
330:
331: /**
332: * location of the target object for the call, relative to the
333: * top of the digester object stack. The default value of zero
334: * means the target object is the one on top of the stack.
335: */
336: protected int targetOffset = 0;
337:
338: /**
339: * The method name to call on the parent object.
340: */
341: protected String methodName = null;
342:
343: /**
344: * The number of parameters to collect from <code>MethodParam</code> rules.
345: * If this value is zero, a single parameter will be collected from the
346: * body of this element.
347: */
348: protected int paramCount = 0;
349:
350: /**
351: * The parameter types of the parameters to be collected.
352: */
353: protected Class paramTypes[] = null;
354:
355: /**
356: * The names of the classes of the parameters to be collected.
357: * This attribute allows creation of the classes to be postponed until the digester is set.
358: */
359: protected String paramClassNames[] = null;
360:
361: /**
362: * Should <code>MethodUtils.invokeExactMethod</code> be used for reflection.
363: */
364: protected boolean useExactMatch = false;
365:
366: // --------------------------------------------------------- Public Methods
367:
368: /**
369: * Should <code>MethodUtils.invokeExactMethod</code>
370: * be used for the reflection.
371: */
372: public boolean getUseExactMatch() {
373: return useExactMatch;
374: }
375:
376: /**
377: * Set whether <code>MethodUtils.invokeExactMethod</code>
378: * should be used for the reflection.
379: */
380: public void setUseExactMatch(boolean useExactMatch) {
381: this .useExactMatch = useExactMatch;
382: }
383:
384: /**
385: * Set the associated digester.
386: * If needed, this class loads the parameter classes from their names.
387: */
388: public void setDigester(Digester digester) {
389: // call superclass
390: super .setDigester(digester);
391: // if necessary, load parameter classes
392: if (this .paramClassNames != null) {
393: this .paramTypes = new Class[paramClassNames.length];
394: for (int i = 0; i < this .paramClassNames.length; i++) {
395: try {
396: this .paramTypes[i] = digester.getClassLoader()
397: .loadClass(this .paramClassNames[i]);
398: } catch (ClassNotFoundException e) {
399: // use the digester log
400: digester.getLogger().error(
401: "(CallMethodRule) Cannot load class "
402: + this .paramClassNames[i], e);
403: this .paramTypes[i] = null; // Will cause NPE later
404: }
405: }
406: }
407: }
408:
409: /**
410: * Process the start of this element.
411: *
412: * @param attributes The attribute list for this element
413: */
414: public void begin(Attributes attributes) throws Exception {
415:
416: // Push an array to capture the parameter values if necessary
417: if (paramCount > 0) {
418: Object parameters[] = new Object[paramCount];
419: for (int i = 0; i < parameters.length; i++) {
420: parameters[i] = null;
421: }
422: digester.pushParams(parameters);
423: }
424:
425: }
426:
427: /**
428: * Process the body text of this element.
429: *
430: * @param bodyText The body text of this element
431: */
432: public void body(String bodyText) throws Exception {
433:
434: if (paramCount == 0) {
435: this .bodyText = bodyText.trim();
436: }
437:
438: }
439:
440: /**
441: * Process the end of this element.
442: */
443: public void end() throws Exception {
444:
445: // Retrieve or construct the parameter values array
446: Object parameters[] = null;
447: if (paramCount > 0) {
448:
449: parameters = (Object[]) digester.popParams();
450:
451: if (digester.log.isTraceEnabled()) {
452: for (int i = 0, size = parameters.length; i < size; i++) {
453: digester.log.trace("[CallMethodRule](" + i + ")"
454: + parameters[i]);
455: }
456: }
457:
458: // In the case where the parameter for the method
459: // is taken from an attribute, and that attribute
460: // isn't actually defined in the source XML file,
461: // skip the method call
462: if (paramCount == 1 && parameters[0] == null) {
463: return;
464: }
465:
466: } else if (paramTypes != null && paramTypes.length != 0) {
467:
468: // In the case where the parameter for the method
469: // is taken from the body text, but there is no
470: // body text included in the source XML file,
471: // skip the method call
472: if (bodyText == null) {
473: return;
474: }
475:
476: parameters = new Object[1];
477: parameters[0] = bodyText;
478: if (paramTypes.length == 0) {
479: paramTypes = new Class[1];
480: paramTypes[0] = "abc".getClass();
481: }
482:
483: }
484:
485: // Construct the parameter values array we will need
486: // We only do the conversion if the param value is a String and
487: // the specified paramType is not String.
488: Object paramValues[] = new Object[paramTypes.length];
489: for (int i = 0; i < paramTypes.length; i++) {
490: // convert nulls and convert stringy parameters
491: // for non-stringy param types
492: if (parameters[i] == null
493: || (parameters[i] instanceof String && !String.class
494: .isAssignableFrom(paramTypes[i]))) {
495:
496: paramValues[i] = IntrospectionUtils.convert(
497: (String) parameters[i], paramTypes[i]);
498: } else {
499: paramValues[i] = parameters[i];
500: }
501: }
502:
503: // Determine the target object for the method call
504: Object target;
505: if (targetOffset >= 0) {
506: target = digester.peek(targetOffset);
507: } else {
508: target = digester.peek(digester.getCount() + targetOffset);
509: }
510:
511: if (target == null) {
512: StringBuffer sb = new StringBuffer();
513: sb.append("[CallMethodRule]{");
514: sb.append(digester.match);
515: sb.append("} Call target is null (");
516: sb.append("targetOffset=");
517: sb.append(targetOffset);
518: sb.append(",stackdepth=");
519: sb.append(digester.getCount());
520: sb.append(")");
521: throw new org.xml.sax.SAXException(sb.toString());
522: }
523:
524: // Invoke the required method on the top object
525: if (digester.log.isDebugEnabled()) {
526: StringBuffer sb = new StringBuffer("[CallMethodRule]{");
527: sb.append(digester.match);
528: sb.append("} Call ");
529: sb.append(target.getClass().getName());
530: sb.append(".");
531: sb.append(methodName);
532: sb.append("(");
533: for (int i = 0; i < paramValues.length; i++) {
534: if (i > 0) {
535: sb.append(",");
536: }
537: if (paramValues[i] == null) {
538: sb.append("null");
539: } else {
540: sb.append(paramValues[i].toString());
541: }
542: sb.append("/");
543: if (paramTypes[i] == null) {
544: sb.append("null");
545: } else {
546: sb.append(paramTypes[i].getName());
547: }
548: }
549: sb.append(")");
550: digester.log.debug(sb.toString());
551: }
552: Object result = IntrospectionUtils.callMethodN(target,
553: methodName, paramValues, paramTypes);
554: processMethodCallResult(result);
555: }
556:
557: /**
558: * Clean up after parsing is complete.
559: */
560: public void finish() throws Exception {
561:
562: bodyText = null;
563:
564: }
565:
566: /**
567: * Subclasses may override this method to perform additional processing of the
568: * invoked method's result.
569: *
570: * @param result the Object returned by the method invoked, possibly null
571: */
572: protected void processMethodCallResult(Object result) {
573: // do nothing
574: }
575:
576: /**
577: * Render a printable version of this Rule.
578: */
579: public String toString() {
580:
581: StringBuffer sb = new StringBuffer("CallMethodRule[");
582: sb.append("methodName=");
583: sb.append(methodName);
584: sb.append(", paramCount=");
585: sb.append(paramCount);
586: sb.append(", paramTypes={");
587: if (paramTypes != null) {
588: for (int i = 0; i < paramTypes.length; i++) {
589: if (i > 0) {
590: sb.append(", ");
591: }
592: sb.append(paramTypes[i].getName());
593: }
594: }
595: sb.append("}");
596: sb.append("]");
597: return (sb.toString());
598:
599: }
600:
601: }
|