KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > beehive > netui > tags > databinding > invoke > AbstractCallMethod


1 /*
2  * Copyright 2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * $Header:$
17  */

18 package org.apache.beehive.netui.tags.databinding.invoke;
19
20 import org.apache.beehive.netui.util.internal.InternalStringBuilder;
21
22 import java.lang.reflect.InvocationTargetException JavaDoc;
23 import java.lang.reflect.Method JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.List JavaDoc;
26 import javax.servlet.jsp.JspException JavaDoc;
27
28 import org.apache.beehive.netui.tags.AbstractClassicTag;
29 import org.apache.beehive.netui.util.Bundle;
30 import org.apache.beehive.netui.util.logging.Logger;
31 import org.apache.beehive.netui.util.type.TypeUtils;
32
33 /**
34  * <p>
35  * An abstract base class for tags that are capable of reflectively invoking methods. Specializations of this
36  * tag provide method implementations that locate the object on which to invoke the method and that handle
37  * any return value from the invoked method.
38  * <p/>
39  * <p>
40  * The <code>CallMethod</code> tag can have child tags of type {@link MethodParameter}; these tags must be in the same
41  * order as the parameter list in the method signature of the method that will be invoked. To invoke an overloaded
42  * method, the {@link MethodParameter#setType(String)} property must be set to the String name of the type to pass
43  * to the method. If the type attribute values on nested {@link MethodParameter} tags do not match any method
44  * signature, an error will be reported in the page.
45  * </p>
46  */

47 public abstract class AbstractCallMethod
48         extends AbstractClassicTag {
49
50     private static final Logger LOGGER = Logger.getInstance(AbstractCallMethod.class);
51
52     private static final Object JavaDoc[] EMPTY_ARGS = new Object JavaDoc[0];
53     private static final String JavaDoc EMPTY_STRING = "";
54     private static final List JavaDoc EMPTY_LIST = new ArrayList JavaDoc();
55
56     private List JavaDoc _parameters = null;
57     private String JavaDoc _method = null;
58     private boolean _failOnError = true;
59     private String JavaDoc _resultId = null;
60     private boolean _verifyTypes = false;
61
62     /**
63      * Sets the identifier at which the result of invoking the method will stored. Once stored, the
64      * result of the reflective invocation will be available via the JSP EL implicit object
65      * <code>${pageScope}</code> with the attribute name set via this property.
66      *
67      * @param resultId a String that names an attribute in the PageContext's
68      * attribute map where any resulting object will be stored.
69      * @jsptagref.attributedescription
70      * Sets the identifier at which the result of invoking the method will stored. Once stored, the
71      * result of the reflective invocation will be available via the JSP EL implicit object
72      * <code>${pageScope}</code> with the attribute name set via this property.
73      * @netui:attribute required="false"
74      */

75     public void setResultId(String JavaDoc resultId) {
76         _resultId = resultId;
77     }
78
79     /**
80      * Sets whether or not to report exceptions to the page when errors occur invoking a method on an object.
81      *
82      * @param failOnError a boolean that defines whether or not exceptions should be thrown when invocation fails.
83      * @jsptagref.attributedescription
84      * Sets whether or not to report exceptions to the page when errors occur invoking a method on an object.
85      * @netui:attribute required="false"
86      */

87     public void setFailOnError(boolean failOnError) {
88         _failOnError = failOnError;
89     }
90
91     /**
92      * Sets the name of a method to invoke on the target object.
93      *
94      * @param method the name of the method to invoke
95      * @jsptagref.attributedescription
96      * Sets the name of a method to invoke on the target object.
97      * @netui:attribute required="true"
98      */

99     public void setMethod(String JavaDoc method) {
100         _method = method;
101     }
102
103     /**
104      * Add a paramter that will be passed as an argument to the method that will be invoked. This method
105      * is implemented to allow the the {@link MethodParameter} tags to register their parameters. This
106      * object is passed in the position that it appeared in the set of child {@link MethodParameter} tags.
107      *
108      * @param type a String of the type or class name of this parameter
109      * @param parameter an object that should be passed as an argument to the invoked method
110      * @see MethodParameter
111      */

112     public void addParameter(String JavaDoc type, Object JavaDoc parameter) {
113         if(_parameters == null)
114             _parameters = new ArrayList JavaDoc();
115
116         // only check the types if necessary
117
if(type != null) _verifyTypes = true;
118
119         ParamNode pn = new ParamNode();
120         pn.typeName = type;
121         pn.paramValue = parameter;
122
123         _parameters.add(pn);
124     }
125
126     /**
127      * Causes the body of this tag to be rendered; only {@link MethodParameter} tags are allowed to be
128      * contained inside of this tag. The output of rendering the body is never written into the
129      * output stream of the servlet.
130      *
131      * @return {@link #EVAL_BODY_BUFFERED}
132      */

133     public int doStartTag()
134             throws JspException JavaDoc {
135         return EVAL_BODY_BUFFERED;
136     }
137
138     /**
139      * Reflectively invokes the method specified by the <code>method</code> attribute,
140      * {@link #findMethod(Object, String, boolean)}. The arguments passed to the method are taken from any nested
141      * {@link MethodParameter} tags. When the parameters which are added by the
142      * {@link MethodParameter} tags are {@link java.lang.String} types, an attempt is made to convert each of
143      * these parameters into the type expected by the method. This conversion is done using the
144      * {@link TypeUtils#convertToObject(java.lang.String, java.lang.Class)} method. If a String can not
145      * be converted do the type expected by the method, an exception is thrown and the error is reported
146      * in the tag. Any return value that results from invoking the given method is passed to the
147      * subclass implementation of the method {@link #handleReturnValue(java.lang.Object)}.
148      *
149      * @return {@link #EVAL_PAGE} to continue evaluating the page
150      * @throws JspException if there are errors. All exceptions that may be thrown
151      * in the process of reflectively invoking the method and performing type
152      * conversion are reported as {@link JspException}
153      * @see #findMethod(Object, String, boolean)
154      * @see #handleReturnValue(java.lang.Object)
155      * @see MethodParameter
156      * @see ObjectNotFoundException
157      * @see TypeUtils#convertToObject(java.lang.String, java.lang.Class)
158      * @see java.lang.String
159      */

160     public int doEndTag()
161             throws JspException JavaDoc {
162         // find the object on which to invoke the method
163
Object JavaDoc object = null;
164         try {
165             object = resolveObject();
166         }
167         catch(ObjectNotFoundException onf) {
168             Throwable JavaDoc cause = (onf.getCause() != null ? onf.getCause() : onf);
169             String JavaDoc msg = Bundle.getErrorString("Tags_AbstractCallMethod_noSuchObject", new Object JavaDoc[]{getObjectName(), _method, cause});
170             registerTagError(msg, null);
171         }
172
173         // if this tag can accept null invocation targets,
174
if(object == null) {
175             if(allowNullInvocationTarget()) {
176                 // each implementation does this on their own
177
handleReturnValue(null);
178                 localRelease();
179                 return EVAL_PAGE;
180             }
181             else {
182                 String JavaDoc msg = Bundle.getErrorString("Tags_AbstractCallMethod_objectIsNull", new Object JavaDoc[]{getObjectName(), _method});
183                 registerTagError(msg, null);
184             }
185         }
186
187         if(hasErrors()) {
188             reportErrors();
189             localRelease();
190             return EVAL_PAGE;
191         }
192
193         Method JavaDoc m = findMethod(object, _method, _verifyTypes);
194
195         if(m == null) {
196             String JavaDoc msg = null;
197             if(_verifyTypes) {
198                 String JavaDoc paramString = prettyPrintParameterTypes(_parameters);
199                 msg = Bundle.getErrorString("Tags_AbstractCallMethod_noSuchMethodWithTypes",
200                         new Object JavaDoc[]{_method,
201                                      (_parameters != null ? new Integer JavaDoc(_parameters.size()) : new Integer JavaDoc(0)),
202                                      paramString,
203                                      getObjectName()});
204             }
205             else
206                 msg = Bundle.getErrorString("Tags_AbstractCallMethod_noSuchMethod",
207                         new Object JavaDoc[]{_method,
208                                      (_parameters != null ? new Integer JavaDoc(_parameters.size()) : new Integer JavaDoc(0)),
209                                      getObjectName()});
210
211             registerTagError(msg, null);
212             reportErrors();
213             localRelease();
214             return EVAL_PAGE;
215         }
216
217         Object JavaDoc[] args = null;
218         try {
219             args = getArguments(m.getParameterTypes());
220         }
221         catch(IllegalArgumentException JavaDoc iae) {
222             registerTagError(iae.getMessage(), null);
223             reportErrors();
224             localRelease();
225             return EVAL_PAGE;
226         }
227         
228         // invoke method
229
Object JavaDoc result = null;
230         try {
231             if(LOGGER.isDebugEnabled()) {
232                 LOGGER.debug("method: " + m.toString());
233                 for(int i = 0; i < args.length; i++)
234                     LOGGER.debug("arg[" + i + "]: " + (args[i] != null ? args[i].getClass().getName() : "null"));
235             }
236
237             result = m.invoke(object, args);
238         }
239         catch(Exception JavaDoc e) {
240             assert e instanceof IllegalAccessException JavaDoc || e instanceof InvocationTargetException JavaDoc || e instanceof IllegalArgumentException JavaDoc;
241
242             if(LOGGER.isErrorEnabled())
243                 LOGGER.error("Could not invoke method \"" + _method + "\" on the object named \"" + getObjectName() + "\" because: " + e, e);
244
245             if(_failOnError) {
246                 String JavaDoc msg = Bundle.getErrorString("Tags_AbstractCallMethod_invocationError", new Object JavaDoc[]{_method, getObjectName(), e});
247                 registerTagError(msg, null);
248                 reportErrors();
249                 localRelease();
250                 return EVAL_PAGE;
251             }
252         }
253
254         if(LOGGER.isDebugEnabled()) {
255             LOGGER.debug((result != null ?
256                     "return value is non-null and is of type \"" + result.getClass().getName() + "\"" :
257                     "return value is null."));
258         }
259
260         // each implementation handles this differently
261
handleReturnValue(result);
262
263         localRelease();
264
265         return EVAL_PAGE;
266     }
267
268     /**
269      * Reset all of the fields of this tag.
270      */

271     protected void localRelease() {
272         super.localRelease();
273         _parameters = null;
274         _method = null;
275         _failOnError = true;
276         _resultId = null;
277         _verifyTypes = false;
278     }
279
280     /**
281      * <p/>
282      * Resolve the object on which the method should be invoked. If there are errors resolving this object,
283      * this method will throw an {@link ObjectNotFoundException}.
284      * </p>
285      * <p>
286      * If the object is not found but no exception occurred, this method returns <code>null</code>.
287      * </p>
288      *
289      * @return the object on which to reflectively invoke the method or <code>null</code> if the
290      * object was not found and no exception occurred.
291      * @throws ObjectNotFoundException if an exception occurred attempting to resolve an object
292      */

293     protected abstract Object JavaDoc resolveObject() throws ObjectNotFoundException, JspException JavaDoc;
294
295     /**
296      * Get the name of the object that is the target of the invocation. This is a generic method for this
297      * tag that enables more specific error reporting.
298      *
299      * @return a name for the object on which the method will be invoked.
300      */

301     protected abstract String JavaDoc getObjectName();
302
303     /**
304      * When implemented to return true, this method allows a tag invoking a method to
305      * accept a null invocation target and simply return null. The default
306      * implementation returns false.
307      *
308      * @return true if the object on which to invoke the method can be null; false otherwise.
309      */

310     protected boolean allowNullInvocationTarget() {
311         return false;
312     }
313
314     /**
315      * <p/>
316      * A method that allows concrete classes to handle the result of the
317      * reflective invocation in an implementation specific way.
318      * </p>
319      * <p/>
320      * The default beahavior is to set the return value resulting from invoking the method
321      * in the {@link javax.servlet.jsp.PageContext} attribute map of the current JSP page.
322      * The result is set as an attribute if the <code>result</code> is not null and the
323      * {@link CallMethod#setResultId(java.lang.String)} String is not null. If the value returned
324      * from calling a method is null and the {@link CallMethod#setResultId(java.lang.String)} is non-null,
325      * the {@link javax.servlet.jsp.PageContext#removeAttribute(java.lang.String)}
326      * is called to remove the attribute from the attribute map.
327      * </p>
328      *
329      * @param result the object that was returned by calling the method on the object
330      */

331     protected void handleReturnValue(Object JavaDoc result) {
332         if(_resultId != null) {
333             if(result != null) {
334                 if(LOGGER.isInfoEnabled() && pageContext.getAttribute(_resultId) != null)
335                     LOGGER.info("Overwriting attribute named \"" + _resultId + "\" in the PageContext with a new attribute of type \"" +
336                             result.getClass().getName() + "\" returned from calling the method \"" + _method + "\" on an object named \"" +
337                             getObjectName() + "\".");
338
339                 pageContext.setAttribute(_resultId, result);
340             }
341             else {
342                 if(LOGGER.isInfoEnabled())
343                     LOGGER.info("Removing attribute named \"" + _resultId + "\" from the PageContext. " +
344                             "The value returned from calling the method \"" + _method + "\" on an object named \"" +
345                             getObjectName() + "\" is null.");
346
347                 pageContext.removeAttribute(_resultId);
348             }
349         }
350     }
351
352     /**
353      * Internal, read-only property used by subclasses to get
354      * the list of parameters to be used when reflectively
355      * invoking a method. If the method takes no parameters, this
356      * list will be of size zero.
357      *
358      * @return the list of parameters
359      */

360     protected List JavaDoc getParameterNodes() {
361         if(_parameters != null)
362             return _parameters;
363         else
364             return EMPTY_LIST;
365     }
366
367     /**
368      * The default findMethod implementation is an uncached search of all
369      * of the methods available on the Class of the <code>target</code>
370      *
371      * @param target the object from which to find the method
372      * @param methodName the name of the method to find
373      * @param verifyTypes a boolean that if true will match the type names in addition to the String method name
374      * @return a Method object matching the methodName and types, if <code>verifyTypes</code> is true.
375      * <code>null</code> otherwise.
376      */

377     protected Method JavaDoc findMethod(Object JavaDoc target, String JavaDoc methodName, boolean verifyTypes) {
378         int paramCount = (_parameters != null ? _parameters.size() : 0);
379         Method JavaDoc[] methods = target.getClass().getMethods();
380
381         for(int i = 0; i < methods.length; i++) {
382             if(methods[i].getName().equals(_method) && methods[i].getParameterTypes().length == paramCount) {
383                 if(LOGGER.isDebugEnabled()) {
384                     LOGGER.debug("found method: " + methods[i]);
385                     LOGGER.debug("check types: " + verifyTypes);
386                 }
387
388                 // page asked to check types
389
if(verifyTypes) {
390                     boolean match = true;
391                     // the lengths of these match b/c of the check above
392
Class JavaDoc[] parameterTypes = methods[i].getParameterTypes();
393                     for(int j = 0; j < parameterTypes.length; j++) {
394                         if(LOGGER.isDebugEnabled()) {
395                             LOGGER.debug("parameterTypes[" + j + "]: " + parameterTypes[j]);
396                             LOGGER.debug("paramNode[" + j + "]: " + ((ParamNode)_parameters.get(j)).typeName);
397                         }
398
399                         // check the name of the class and the name of the parameter type
400
if(!parameterTypes[j].getName().equals(((ParamNode)_parameters.get(j)).typeName)) {
401                             match = false;
402                             break;
403                         }
404
405                     }
406                     if(match) return methods[i];
407                 }
408                 else
409                     return methods[i];
410             }
411         }
412
413         return null;
414     }
415
416     /**
417      * Convert the arguments for a method from Strings set as attributes
418      * on JSP tags to the types represented by teh list of Class[] objects
419      * provided here.
420      *
421      * @return an Object[] that contains the parameters to pass to the method
422      * @throws IllegalArgumentException if an error occurs converting an
423      * argument to a specific type.
424      */

425     private final Object JavaDoc[] getArguments(Class JavaDoc[] paramTypes) {
426         if(_parameters == null)
427             return EMPTY_ARGS;
428
429         Object JavaDoc[] args = new Object JavaDoc[paramTypes.length];
430
431         for(int i = 0; i < _parameters.size(); i++) {
432             ParamNode pn = (ParamNode)_parameters.get(i);
433             // if the parameter should have been null, leave it null
434
if(pn.paramValue == MethodParameter.NULL_ARG)
435                 continue;
436
437             Object JavaDoc value = pn.paramValue;
438             try {
439                 // if the value wasn't a String, it may have come from EL so don't try to convert it
440
if(!(value instanceof String JavaDoc) || value == null)
441                     args[i] = value;
442                 // here, there's a non-null String value
443
else
444                     args[i] = TypeUtils.convertToObject((String JavaDoc)value, paramTypes[i]);
445             }
446                     // catch Exception here because almost anything can be thrown by TypeUtils.convertToObject().
447
catch(Exception JavaDoc e) {
448                 String JavaDoc msg = Bundle.getErrorString("Tags_AbstractCallMethod_parameterError",
449                         new Object JavaDoc[]{paramTypes[i], new Integer JavaDoc(i), value, e.toString()});
450                 throw new IllegalArgumentException JavaDoc(msg);
451             }
452
453             if(LOGGER.isDebugEnabled()) LOGGER.debug("argTypes[" + i + "]: " + paramTypes[i]);
454         }
455
456         return args;
457     }
458
459     /**
460      * Utility method that pretty-prints the types of the parameters
461      * passed to a method; this is used in debugging.
462      *
463      * @param parameters the list of parameters
464      * @return a String that represents the types of each of these paramters in order
465      */

466     private static final String JavaDoc prettyPrintParameterTypes(List JavaDoc parameters) {
467         InternalStringBuilder paramString = null;
468         if(parameters != null) {
469             paramString = new InternalStringBuilder(128);
470             paramString.append("(");
471             for(int i = 0; i < parameters.size(); i++) {
472                 if(i > 0)
473                     paramString.append(", ");
474
475                 paramString.append(((ParamNode)parameters.get(i)).typeName);
476             }
477             paramString.append(")");
478
479             return paramString.toString();
480         }
481         else
482             return EMPTY_STRING;
483     }
484
485     /**
486      * An internal struct that represents a parameter that will be passed to a
487      * reflective method invocation call. Instances of <code>ParamNode</code>
488      * map 1:1 to the methodParameter tags that appear within the body of
489      * an AbstrctCallMethod tag.
490      *
491      * @exclude
492      */

493     protected class ParamNode {
494
495         /**
496          * The fully qualified class name of the parameter type. This value
497          * can be null if parameter type checking does not need to occur.
498          */

499         public String JavaDoc typeName = null;
500
501         /**
502          * The value of the parameter. Often, this is a String expression
503          * which is evaluated later and converted into some Object
504          * type such as Integer or Foobar.
505          */

506         public Object JavaDoc paramValue = null;
507
508         public String JavaDoc toString() {
509             InternalStringBuilder buf = new InternalStringBuilder(32);
510             buf.append("typeName: ").append(typeName);
511             return buf.toString();
512         }
513     }
514 }
515
Popular Tags