001 /* $Id: CallMethodRule.java 992060 2010-09-02 19:09:47Z simonetripodi $ 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 @Deprecated 113 public CallMethodRule(Digester digester, String methodName, 114 int paramCount) { 115 116 this(methodName, paramCount); 117 118 } 119 120 121 /** 122 * Construct a "call method" rule with the specified method name. 123 * 124 * @param digester The associated Digester 125 * @param methodName Method name of the parent method to call 126 * @param paramCount The number of parameters to collect, or 127 * zero for a single argument from the body of ths element 128 * @param paramTypes The Java class names of the arguments 129 * (if you wish to use a primitive type, specify the corresonding 130 * Java wrapper class instead, such as <code>java.lang.Boolean</code> 131 * for a <code>boolean</code> parameter) 132 * 133 * @deprecated The digester instance is now set in the {@link Digester#addRule} method. 134 * Use {@link #CallMethodRule(String methodName,int paramCount, String [] paramTypes)} instead. 135 */ 136 @Deprecated 137 public CallMethodRule(Digester digester, String methodName, 138 int paramCount, String paramTypes[]) { 139 140 this(methodName, paramCount, paramTypes); 141 142 } 143 144 145 /** 146 * Construct a "call method" rule with the specified method name. 147 * 148 * @param digester The associated Digester 149 * @param methodName Method name of the parent method to call 150 * @param paramCount The number of parameters to collect, or 151 * zero for a single argument from the body of ths element 152 * @param paramTypes The Java classes that represent the 153 * parameter types of the method arguments 154 * (if you wish to use a primitive type, specify the corresonding 155 * Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code> 156 * for a <code>boolean</code> parameter) 157 * 158 * @deprecated The digester instance is now set in the {@link Digester#addRule} method. 159 * Use {@link #CallMethodRule(String methodName,int paramCount, Class [] paramTypes)} instead. 160 */ 161 @Deprecated 162 public CallMethodRule(Digester digester, String methodName, 163 int paramCount, Class<?> paramTypes[]) { 164 165 this(methodName, paramCount, paramTypes); 166 } 167 168 169 /** 170 * Construct a "call method" rule with the specified method name. The 171 * parameter types (if any) default to java.lang.String. 172 * 173 * @param methodName Method name of the parent method to call 174 * @param paramCount The number of parameters to collect, or 175 * zero for a single argument from the body of this element. 176 */ 177 public CallMethodRule(String methodName, 178 int paramCount) { 179 this(0, methodName, paramCount); 180 } 181 182 /** 183 * Construct a "call method" rule with the specified method name. The 184 * parameter types (if any) default to java.lang.String. 185 * 186 * @param targetOffset location of the target object. Positive numbers are 187 * relative to the top of the digester object stack. Negative numbers 188 * are relative to the bottom of the stack. Zero implies the top 189 * object on the stack. 190 * @param methodName Method name of the parent method to call 191 * @param paramCount The number of parameters to collect, or 192 * zero for a single argument from the body of this element. 193 */ 194 public CallMethodRule(int targetOffset, 195 String methodName, 196 int paramCount) { 197 198 this.targetOffset = targetOffset; 199 this.methodName = methodName; 200 this.paramCount = paramCount; 201 if (paramCount == 0) { 202 this.paramTypes = new Class[] { String.class }; 203 } else { 204 this.paramTypes = new Class[paramCount]; 205 for (int i = 0; i < this.paramTypes.length; i++) { 206 this.paramTypes[i] = String.class; 207 } 208 } 209 210 } 211 212 /** 213 * Construct a "call method" rule with the specified method name. 214 * The method should accept no parameters. 215 * 216 * @param methodName Method name of the parent method to call 217 */ 218 public CallMethodRule(String methodName) { 219 220 this(0, methodName, 0, (Class[]) null); 221 222 } 223 224 225 /** 226 * Construct a "call method" rule with the specified method name. 227 * The method should accept no parameters. 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 */ 235 public CallMethodRule(int targetOffset, String methodName) { 236 237 this(targetOffset, methodName, 0, (Class[]) null); 238 239 } 240 241 242 /** 243 * Construct a "call method" rule with the specified method name and 244 * parameter types. If <code>paramCount</code> is set to zero the rule 245 * will use the body of this element as the single argument of the 246 * method, unless <code>paramTypes</code> is null or empty, in this 247 * case the rule will call the specified method with no arguments. 248 * 249 * @param methodName Method name of the parent method to call 250 * @param paramCount The number of parameters to collect, or 251 * zero for a single argument from the body of ths element 252 * @param paramTypes The Java class names of the arguments 253 * (if you wish to use a primitive type, specify the corresonding 254 * Java wrapper class instead, such as <code>java.lang.Boolean</code> 255 * for a <code>boolean</code> parameter) 256 */ 257 public CallMethodRule( 258 String methodName, 259 int paramCount, 260 String paramTypes[]) { 261 this(0, methodName, paramCount, paramTypes); 262 } 263 264 /** 265 * Construct a "call method" rule with the specified method name and 266 * parameter types. If <code>paramCount</code> is set to zero the rule 267 * will use the body of this element as the single argument of the 268 * method, unless <code>paramTypes</code> is null or empty, in this 269 * case the rule will call the specified method with no arguments. 270 * 271 * @param targetOffset location of the target object. Positive numbers are 272 * relative to the top of the digester object stack. Negative numbers 273 * are relative to the bottom of the stack. Zero implies the top 274 * object on the stack. 275 * @param methodName Method name of the parent method to call 276 * @param paramCount The number of parameters to collect, or 277 * zero for a single argument from the body of ths element 278 * @param paramTypes The Java class names of the arguments 279 * (if you wish to use a primitive type, specify the corresonding 280 * Java wrapper class instead, such as <code>java.lang.Boolean</code> 281 * for a <code>boolean</code> parameter) 282 */ 283 public CallMethodRule( int targetOffset, 284 String methodName, 285 int paramCount, 286 String paramTypes[]) { 287 288 this.targetOffset = targetOffset; 289 this.methodName = methodName; 290 this.paramCount = paramCount; 291 if (paramTypes == null) { 292 this.paramTypes = new Class[paramCount]; 293 for (int i = 0; i < this.paramTypes.length; i++) { 294 this.paramTypes[i] = String.class; 295 } 296 } else { 297 // copy the parameter class names into an array 298 // the classes will be loaded when the digester is set 299 this.paramClassNames = new String[paramTypes.length]; 300 for (int i = 0; i < this.paramClassNames.length; i++) { 301 this.paramClassNames[i] = paramTypes[i]; 302 } 303 } 304 305 } 306 307 308 /** 309 * Construct a "call method" rule with the specified method name and 310 * parameter types. If <code>paramCount</code> is set to zero the rule 311 * will use the body of this element as the single argument of the 312 * method, unless <code>paramTypes</code> is null or empty, in this 313 * case the rule will call the specified method with no arguments. 314 * 315 * @param methodName Method name of the parent method to call 316 * @param paramCount The number of parameters to collect, or 317 * zero for a single argument from the body of ths element 318 * @param paramTypes The Java classes that represent the 319 * parameter types of the method arguments 320 * (if you wish to use a primitive type, specify the corresonding 321 * Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code> 322 * for a <code>boolean</code> parameter) 323 */ 324 public CallMethodRule( 325 String methodName, 326 int paramCount, 327 Class<?> paramTypes[]) { 328 this(0, methodName, paramCount, paramTypes); 329 } 330 331 /** 332 * Construct a "call method" rule with the specified method name and 333 * parameter types. If <code>paramCount</code> is set to zero the rule 334 * will use the body of this element as the single argument of the 335 * method, unless <code>paramTypes</code> is null or empty, in this 336 * case the rule will call the specified method with no arguments. 337 * 338 * @param targetOffset location of the target object. Positive numbers are 339 * relative to the top of the digester object stack. Negative numbers 340 * are relative to the bottom of the stack. Zero implies the top 341 * object on the stack. 342 * @param methodName Method name of the parent method to call 343 * @param paramCount The number of parameters to collect, or 344 * zero for a single argument from the body of ths element 345 * @param paramTypes The Java classes that represent the 346 * parameter types of the method arguments 347 * (if you wish to use a primitive type, specify the corresonding 348 * Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code> 349 * for a <code>boolean</code> parameter) 350 */ 351 public CallMethodRule( int targetOffset, 352 String methodName, 353 int paramCount, 354 Class<?> paramTypes[]) { 355 356 this.targetOffset = targetOffset; 357 this.methodName = methodName; 358 this.paramCount = paramCount; 359 if (paramTypes == null) { 360 this.paramTypes = new Class[paramCount]; 361 for (int i = 0; i < this.paramTypes.length; i++) { 362 this.paramTypes[i] = String.class; 363 } 364 } else { 365 this.paramTypes = new Class[paramTypes.length]; 366 for (int i = 0; i < this.paramTypes.length; i++) { 367 this.paramTypes[i] = paramTypes[i]; 368 } 369 } 370 371 } 372 373 374 // ----------------------------------------------------- Instance Variables 375 376 377 /** 378 * The body text collected from this element. 379 */ 380 protected String bodyText = null; 381 382 383 /** 384 * location of the target object for the call, relative to the 385 * top of the digester object stack. The default value of zero 386 * means the target object is the one on top of the stack. 387 */ 388 protected int targetOffset = 0; 389 390 /** 391 * The method name to call on the parent object. 392 */ 393 protected String methodName = null; 394 395 396 /** 397 * The number of parameters to collect from <code>MethodParam</code> rules. 398 * If this value is zero, a single parameter will be collected from the 399 * body of this element. 400 */ 401 protected int paramCount = 0; 402 403 404 /** 405 * The parameter types of the parameters to be collected. 406 */ 407 protected Class<?> paramTypes[] = null; 408 409 /** 410 * The names of the classes of the parameters to be collected. 411 * This attribute allows creation of the classes to be postponed until the digester is set. 412 */ 413 private String paramClassNames[] = null; 414 415 /** 416 * Should <code>MethodUtils.invokeExactMethod</code> be used for reflection. 417 */ 418 protected boolean useExactMatch = false; 419 420 // --------------------------------------------------------- Public Methods 421 422 /** 423 * Should <code>MethodUtils.invokeExactMethod</code> 424 * be used for the reflection. 425 */ 426 public boolean getUseExactMatch() { 427 return useExactMatch; 428 } 429 430 /** 431 * Set whether <code>MethodUtils.invokeExactMethod</code> 432 * should be used for the reflection. 433 */ 434 public void setUseExactMatch(boolean useExactMatch) 435 { 436 this.useExactMatch = useExactMatch; 437 } 438 439 /** 440 * Set the associated digester. 441 * If needed, this class loads the parameter classes from their names. 442 */ 443 @Override 444 public void setDigester(Digester digester) 445 { 446 // call superclass 447 super.setDigester(digester); 448 // if necessary, load parameter classes 449 if (this.paramClassNames != null) { 450 this.paramTypes = new Class[paramClassNames.length]; 451 for (int i = 0; i < this.paramClassNames.length; i++) { 452 try { 453 this.paramTypes[i] = 454 digester.getClassLoader().loadClass(this.paramClassNames[i]); 455 } catch (ClassNotFoundException e) { 456 // use the digester log 457 digester.getLogger().error("(CallMethodRule) Cannot load class " + this.paramClassNames[i], e); 458 this.paramTypes[i] = null; // Will cause NPE later 459 } 460 } 461 } 462 } 463 464 /** 465 * Process the start of this element. 466 * 467 * @param attributes The attribute list for this element 468 */ 469 @Override 470 public void begin(Attributes attributes) throws Exception { 471 472 // Push an array to capture the parameter values if necessary 473 if (paramCount > 0) { 474 Object parameters[] = new Object[paramCount]; 475 for (int i = 0; i < parameters.length; i++) { 476 parameters[i] = null; 477 } 478 digester.pushParams(parameters); 479 } 480 481 } 482 483 484 /** 485 * Process the body text of this element. 486 * 487 * @param bodyText The body text of this element 488 */ 489 @Override 490 public void body(String bodyText) throws Exception { 491 492 if (paramCount == 0) { 493 this.bodyText = bodyText.trim(); 494 } 495 496 } 497 498 499 /** 500 * Process the end of this element. 501 */ 502 @Override 503 public void end() throws Exception { 504 505 // Retrieve or construct the parameter values array 506 Object parameters[] = null; 507 if (paramCount > 0) { 508 509 parameters = (Object[]) digester.popParams(); 510 511 if (digester.log.isTraceEnabled()) { 512 for (int i=0,size=parameters.length;i<size;i++) { 513 digester.log.trace("[CallMethodRule](" + i + ")" + parameters[i]) ; 514 } 515 } 516 517 // In the case where the target method takes a single parameter 518 // and that parameter does not exist (the CallParamRule never 519 // executed or the CallParamRule was intended to set the parameter 520 // from an attribute but the attribute wasn't present etc) then 521 // skip the method call. 522 // 523 // This is useful when a class has a "default" value that should 524 // only be overridden if data is present in the XML. I don't 525 // know why this should only apply to methods taking *one* 526 // parameter, but it always has been so we can't change it now. 527 if (paramCount == 1 && parameters[0] == null) { 528 return; 529 } 530 531 } else if (paramTypes != null && paramTypes.length != 0) { 532 // Having paramCount == 0 and paramTypes.length == 1 indicates 533 // that we have the special case where the target method has one 534 // parameter being the body text of the current element. 535 536 // There is no body text included in the source XML file, 537 // so skip the method call 538 if (bodyText == null) { 539 return; 540 } 541 542 parameters = new Object[1]; 543 parameters[0] = bodyText; 544 if (paramTypes.length == 0) { 545 paramTypes = new Class[1]; 546 paramTypes[0] = String.class; 547 } 548 549 } else { 550 // When paramCount is zero and paramTypes.length is zero it 551 // means that we truly are calling a method with no parameters. 552 // Nothing special needs to be done here. 553 } 554 555 // Construct the parameter values array we will need 556 // We only do the conversion if the param value is a String and 557 // the specified paramType is not String. 558 Object paramValues[] = new Object[paramTypes.length]; 559 for (int i = 0; i < paramTypes.length; i++) { 560 // convert nulls and convert stringy parameters 561 // for non-stringy param types 562 if( 563 parameters[i] == null || 564 (parameters[i] instanceof String && 565 !String.class.isAssignableFrom(paramTypes[i]))) { 566 567 paramValues[i] = 568 ConvertUtils.convert((String) parameters[i], paramTypes[i]); 569 } else { 570 paramValues[i] = parameters[i]; 571 } 572 } 573 574 // Determine the target object for the method call 575 Object target; 576 if (targetOffset >= 0) { 577 target = digester.peek(targetOffset); 578 } else { 579 target = digester.peek( digester.getCount() + targetOffset ); 580 } 581 582 if (target == null) { 583 StringBuffer sb = new StringBuffer(); 584 sb.append("[CallMethodRule]{"); 585 sb.append(digester.match); 586 sb.append("} Call target is null ("); 587 sb.append("targetOffset="); 588 sb.append(targetOffset); 589 sb.append(",stackdepth="); 590 sb.append(digester.getCount()); 591 sb.append(")"); 592 throw new org.xml.sax.SAXException(sb.toString()); 593 } 594 595 // Invoke the required method on the top object 596 if (digester.log.isDebugEnabled()) { 597 StringBuffer sb = new StringBuffer("[CallMethodRule]{"); 598 sb.append(digester.match); 599 sb.append("} Call "); 600 sb.append(target.getClass().getName()); 601 sb.append("."); 602 sb.append(methodName); 603 sb.append("("); 604 for (int i = 0; i < paramValues.length; i++) { 605 if (i > 0) { 606 sb.append(","); 607 } 608 if (paramValues[i] == null) { 609 sb.append("null"); 610 } else { 611 sb.append(paramValues[i].toString()); 612 } 613 sb.append("/"); 614 if (paramTypes[i] == null) { 615 sb.append("null"); 616 } else { 617 sb.append(paramTypes[i].getName()); 618 } 619 } 620 sb.append(")"); 621 digester.log.debug(sb.toString()); 622 } 623 624 Object result = null; 625 if (useExactMatch) { 626 // invoke using exact match 627 result = MethodUtils.invokeExactMethod(target, methodName, 628 paramValues, paramTypes); 629 630 } else { 631 // invoke using fuzzier match 632 result = MethodUtils.invokeMethod(target, methodName, 633 paramValues, paramTypes); 634 } 635 636 processMethodCallResult(result); 637 } 638 639 640 /** 641 * Clean up after parsing is complete. 642 */ 643 @Override 644 public void finish() throws Exception { 645 646 bodyText = null; 647 648 } 649 650 /** 651 * Subclasses may override this method to perform additional processing of the 652 * invoked method's result. 653 * 654 * @param result the Object returned by the method invoked, possibly null 655 */ 656 protected void processMethodCallResult(Object result) { 657 // do nothing 658 } 659 660 /** 661 * Render a printable version of this Rule. 662 */ 663 @Override 664 public String toString() { 665 666 StringBuffer sb = new StringBuffer("CallMethodRule["); 667 sb.append("methodName="); 668 sb.append(methodName); 669 sb.append(", paramCount="); 670 sb.append(paramCount); 671 sb.append(", paramTypes={"); 672 if (paramTypes != null) { 673 for (int i = 0; i < paramTypes.length; i++) { 674 if (i > 0) { 675 sb.append(", "); 676 } 677 sb.append(paramTypes[i].getName()); 678 } 679 } 680 sb.append("}"); 681 sb.append("]"); 682 return (sb.toString()); 683 684 } 685 686 687 }