001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * 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     * $Id: ExtensionHandlerJavaClass.java 469672 2006-10-31 21:56:19Z minchau $
020     */
021    
022    package org.apache.xalan.extensions;
023    
024    import java.io.IOException;
025    import java.lang.reflect.Constructor;
026    import java.lang.reflect.InvocationTargetException;
027    import java.lang.reflect.Method;
028    import java.lang.reflect.Modifier;
029    import java.util.Vector;
030    
031    import javax.xml.transform.TransformerException;
032    
033    import org.apache.xalan.templates.ElemTemplateElement;
034    import org.apache.xalan.templates.Stylesheet;
035    import org.apache.xalan.trace.ExtensionEvent;
036    import org.apache.xalan.transformer.TransformerImpl;
037    import org.apache.xpath.functions.FuncExtFunction;
038    import org.apache.xpath.objects.XObject;
039    
040    /**
041     * Represents an extension namespace for XPath that handles java classes.
042     * It is recommended that the class URI be of the form:
043     * <pre>
044     *   xalan://fully.qualified.class.name
045     * </pre>
046     * However, we do not enforce this.  If the class name contains a
047     * a /, we only use the part to the right of the rightmost slash.
048     * In addition, we ignore any "class:" prefix.
049     * Provides functions to test a function's existence and call a function.
050     * Also provides functions to test an element's existence and call an
051     * element.
052     *
053     * @author <a href="mailto:garyp@firstech.com">Gary L Peskin</a>
054     * @xsl.usage internal
055     */
056    
057    public class ExtensionHandlerJavaClass extends ExtensionHandlerJava
058    {
059    
060      private Class m_classObj = null;
061    
062      /**
063       * Provides a default Instance for use by elements that need to call 
064       * an instance method.
065       */
066    
067      private Object m_defaultInstance = null;
068    
069    
070      /**
071       * Construct a new extension namespace handler given all the information
072       * needed. 
073       * @param namespaceUri the extension namespace URI that I'm implementing
074       * @param scriptLang   language of code implementing the extension
075       * @param className    the fully qualified class name of the class
076       */
077      public ExtensionHandlerJavaClass(String namespaceUri,
078                                       String scriptLang,
079                                       String className)
080      {
081        super(namespaceUri, scriptLang, className);
082        try
083        {
084          m_classObj = getClassForName(className);
085        }
086        catch (ClassNotFoundException e)
087        {
088          // For now, just let this go.  We'll catch it when we try to invoke a method.
089        }
090      }
091    
092    
093      /**
094       * Tests whether a certain function name is known within this namespace.
095       * Simply looks for a method with the appropriate name.  There is
096       * no information regarding the arguments to the function call or
097       * whether the method implementing the function is a static method or
098       * an instance method.
099       * @param function name of the function being tested
100       * @return true if its known, false if not.
101       */
102    
103      public boolean isFunctionAvailable(String function) 
104      {
105        Method[] methods = m_classObj.getMethods();
106        int nMethods = methods.length;
107        for (int i = 0; i < nMethods; i++)
108        {
109          if (methods[i].getName().equals(function))
110            return true;
111        }
112        return false;
113      }
114    
115    
116      /**
117       * Tests whether a certain element name is known within this namespace.
118       * Looks for a method with the appropriate name and signature.
119       * This method examines both static and instance methods.
120       * @param element name of the element being tested
121       * @return true if its known, false if not.
122       */
123    
124      public boolean isElementAvailable(String element) 
125      {
126        Method[] methods = m_classObj.getMethods();
127        int nMethods = methods.length;
128        for (int i = 0; i < nMethods; i++)
129        {
130          if (methods[i].getName().equals(element))
131          {
132            Class[] paramTypes = methods[i].getParameterTypes();
133            if ( (paramTypes.length == 2)
134              && paramTypes[0].isAssignableFrom(
135                            org.apache.xalan.extensions.XSLProcessorContext.class)
136              && paramTypes[1].isAssignableFrom(
137                            org.apache.xalan.templates.ElemExtensionCall.class) )
138            {
139              return true;
140            }
141          }
142        }
143        return false;
144      }
145      
146      /**
147       * Process a call to a function in the java class represented by
148       * this <code>ExtensionHandlerJavaClass<code>.
149       * There are three possible types of calls:
150       * <pre>
151       *   Constructor:
152       *     classns:new(arg1, arg2, ...)
153       *
154       *   Static method:
155       *     classns:method(arg1, arg2, ...)
156       *
157       *   Instance method:
158       *     classns:method(obj, arg1, arg2, ...)
159       * </pre>
160       * We use the following rules to determine the type of call made:
161       * <ol type="1">
162       * <li>If the function name is "new", call the best constructor for
163       *     class represented by the namespace URI</li>
164       * <li>If the first argument to the function is of the class specified
165       *     in the namespace or is a subclass of that class, look for the best
166       *     method of the class specified in the namespace with the specified
167       *     arguments.  Compare all static and instance methods with the correct
168       *     method name.  For static methods, use all arguments in the compare.
169       *     For instance methods, use all arguments after the first.</li>
170       * <li>Otherwise, select the best static or instance method matching
171       *     all of the arguments.  If the best method is an instance method,
172       *     call the function using a default object, creating it if needed.</li>
173       * </ol>
174       *
175       * @param funcName Function name.
176       * @param args     The arguments of the function call.
177       * @param methodKey A key that uniquely identifies this class and method call.
178       * @param exprContext The context in which this expression is being executed.
179       * @return the return value of the function evaluation.
180       * @throws TransformerException
181       */
182    
183      public Object callFunction (String funcName, 
184                                  Vector args, 
185                                  Object methodKey,
186                                  ExpressionContext exprContext)
187        throws TransformerException 
188      {
189    
190        Object[] methodArgs;
191        Object[][] convertedArgs;
192        Class[] paramTypes;
193    
194        try
195        {
196          TransformerImpl trans = (exprContext != null) ?
197              (TransformerImpl)exprContext.getXPathContext().getOwnerObject() : null;
198          if (funcName.equals("new")) {                   // Handle constructor call
199    
200            methodArgs = new Object[args.size()];
201            convertedArgs = new Object[1][];
202            for (int i = 0; i < methodArgs.length; i++)
203            {
204              methodArgs[i] = args.get(i);
205            }
206            Constructor c = null;
207            if (methodKey != null)
208              c = (Constructor) getFromCache(methodKey, null, methodArgs);
209            
210            if (c != null && !trans.getDebug())
211            {
212              try
213              {
214                paramTypes = c.getParameterTypes();
215                MethodResolver.convertParams(methodArgs, convertedArgs, 
216                            paramTypes, exprContext);
217                return c.newInstance(convertedArgs[0]);
218              }
219              catch (InvocationTargetException ite)
220              {
221                throw ite;
222              }
223              catch(Exception e)
224              {
225                // Must not have been the right one
226              }
227            }
228            c = MethodResolver.getConstructor(m_classObj, 
229                                              methodArgs,
230                                              convertedArgs,
231                                              exprContext);
232            if (methodKey != null)
233              putToCache(methodKey, null, methodArgs, c);
234            
235            if (trans != null && trans.getDebug()) {            
236                trans.getTraceManager().fireExtensionEvent(new 
237                        ExtensionEvent(trans, c, convertedArgs[0]));
238                Object result;
239                try {            
240                    result = c.newInstance(convertedArgs[0]);
241                } catch (Exception e) {
242                    throw e;
243                } finally {
244                    trans.getTraceManager().fireExtensionEndEvent(new 
245                            ExtensionEvent(trans, c, convertedArgs[0]));
246                }
247                return result;
248            } else
249                return c.newInstance(convertedArgs[0]);
250          }
251    
252          else
253          {
254    
255            int resolveType;
256            Object targetObject = null;
257            methodArgs = new Object[args.size()];
258            convertedArgs = new Object[1][];
259            for (int i = 0; i < methodArgs.length; i++)
260            {
261              methodArgs[i] = args.get(i);
262            }
263            Method m = null;
264            if (methodKey != null)
265              m = (Method) getFromCache(methodKey, null, methodArgs);
266            
267            if (m != null && !trans.getDebug())
268            {
269              try
270              {
271                paramTypes = m.getParameterTypes();
272                MethodResolver.convertParams(methodArgs, convertedArgs, 
273                            paramTypes, exprContext);
274                if (Modifier.isStatic(m.getModifiers()))
275                  return m.invoke(null, convertedArgs[0]);
276                else
277                {
278                  // This is tricky.  We get the actual number of target arguments (excluding any
279                  //   ExpressionContext).  If we passed in the same number, we need the implied object.
280                  int nTargetArgs = convertedArgs[0].length;
281                  if (ExpressionContext.class.isAssignableFrom(paramTypes[0]))
282                    nTargetArgs--;
283                  if (methodArgs.length <= nTargetArgs)
284                    return m.invoke(m_defaultInstance, convertedArgs[0]);
285                  else  
286                  {
287                    targetObject = methodArgs[0];
288                    
289                    if (targetObject instanceof XObject)
290                      targetObject = ((XObject) targetObject).object();
291                      
292                    return m.invoke(targetObject, convertedArgs[0]);
293                  }
294                }
295              }
296              catch (InvocationTargetException ite)
297              {
298                throw ite;
299              }
300              catch(Exception e)
301              {
302                // Must not have been the right one
303              }
304            }
305    
306            if (args.size() > 0)
307            {
308              targetObject = methodArgs[0];
309    
310              if (targetObject instanceof XObject)
311                targetObject = ((XObject) targetObject).object();
312    
313              if (m_classObj.isAssignableFrom(targetObject.getClass()))
314                resolveType = MethodResolver.DYNAMIC;
315              else
316                resolveType = MethodResolver.STATIC_AND_INSTANCE;
317            }
318            else
319            {
320              targetObject = null;
321              resolveType = MethodResolver.STATIC_AND_INSTANCE;
322            }
323    
324            m = MethodResolver.getMethod(m_classObj,
325                                         funcName,
326                                         methodArgs, 
327                                         convertedArgs,
328                                         exprContext,
329                                         resolveType);
330            if (methodKey != null)
331              putToCache(methodKey, null, methodArgs, m);
332    
333            if (MethodResolver.DYNAMIC == resolveType) {         // First argument was object type
334              if (trans != null && trans.getDebug()) {
335                trans.getTraceManager().fireExtensionEvent(m, targetObject, 
336                            convertedArgs[0]);
337                Object result;
338                try {
339                    result = m.invoke(targetObject, convertedArgs[0]);
340                } catch (Exception e) {
341                    throw e;
342                } finally {
343                    trans.getTraceManager().fireExtensionEndEvent(m, targetObject, 
344                            convertedArgs[0]);
345                }
346                return result;
347              } else                  
348                return m.invoke(targetObject, convertedArgs[0]);
349            }
350            else                                  // First arg was not object.  See if we need the implied object.
351            {
352              if (Modifier.isStatic(m.getModifiers())) {
353                if (trans != null && trans.getDebug()) {
354                  trans.getTraceManager().fireExtensionEvent(m, null, 
355                            convertedArgs[0]);
356                  Object result;
357                  try {
358                      result = m.invoke(null, convertedArgs[0]);
359                  } catch (Exception e) {
360                    throw e;
361                  } finally {
362                    trans.getTraceManager().fireExtensionEndEvent(m, null, 
363                            convertedArgs[0]);
364                  }
365                  return result;
366                } else                  
367                  return m.invoke(null, convertedArgs[0]);
368              }
369              else
370              {
371                if (null == m_defaultInstance)
372                {
373                  if (trans != null && trans.getDebug()) {
374                    trans.getTraceManager().fireExtensionEvent(new 
375                            ExtensionEvent(trans, m_classObj));
376                    try {
377                        m_defaultInstance = m_classObj.newInstance();
378                    } catch (Exception e) {
379                        throw e;
380                    } finally {
381                        trans.getTraceManager().fireExtensionEndEvent(new 
382                            ExtensionEvent(trans, m_classObj));
383                    }
384                  }    else
385                      m_defaultInstance = m_classObj.newInstance();
386                }
387                if (trans != null && trans.getDebug()) {
388                  trans.getTraceManager().fireExtensionEvent(m, m_defaultInstance, 
389                        convertedArgs[0]);
390                  Object result;
391                  try {
392                    result = m.invoke(m_defaultInstance, convertedArgs[0]);
393                  } catch (Exception e) {
394                    throw e;
395                  } finally {
396                    trans.getTraceManager().fireExtensionEndEvent(m, 
397                            m_defaultInstance, convertedArgs[0]);
398                  }
399                  return result;
400                } else                  
401                  return m.invoke(m_defaultInstance, convertedArgs[0]);
402              }  
403            }
404    
405          }
406        }
407        catch (InvocationTargetException ite)
408        {
409          Throwable resultException = ite;
410          Throwable targetException = ite.getTargetException();
411     
412          if (targetException instanceof TransformerException)
413            throw ((TransformerException)targetException);
414          else if (targetException != null)
415            resultException = targetException;
416                
417          throw new TransformerException(resultException);
418        }
419        catch (Exception e)
420        {
421          // e.printStackTrace();
422          throw new TransformerException(e);
423        }
424      }
425    
426      /**
427       * Process a call to an XPath extension function
428       *
429       * @param extFunction The XPath extension function
430       * @param args The arguments of the function call.
431       * @param exprContext The context in which this expression is being executed.
432       * @return the return value of the function evaluation.
433       * @throws TransformerException
434       */
435      public Object callFunction(FuncExtFunction extFunction,
436                                 Vector args,
437                                 ExpressionContext exprContext)
438          throws TransformerException
439      {
440        return callFunction(extFunction.getFunctionName(), args, 
441                            extFunction.getMethodKey(), exprContext);
442      }
443    
444      /**
445       * Process a call to this extension namespace via an element. As a side
446       * effect, the results are sent to the TransformerImpl's result tree. 
447       * We invoke the static or instance method in the class represented by
448       * by the namespace URI.  If we don't already have an instance of this class,
449       * we create one upon the first call.
450       *
451       * @param localPart      Element name's local part.
452       * @param element        The extension element being processed.
453       * @param transformer      Handle to TransformerImpl.
454       * @param stylesheetTree The compiled stylesheet tree.
455       * @param methodKey      A key that uniquely identifies this element call.
456       * @throws IOException           if loading trouble
457       * @throws TransformerException          if parsing trouble
458       */
459    
460      public void processElement(String localPart,
461                                 ElemTemplateElement element,
462                                 TransformerImpl transformer,
463                                 Stylesheet stylesheetTree,
464                                 Object methodKey)
465        throws TransformerException, IOException
466      {
467        Object result = null;
468    
469        Method m = (Method) getFromCache(methodKey, null, null);
470        if (null == m)
471        {
472          try
473          {
474            m = MethodResolver.getElementMethod(m_classObj, localPart);
475            if ( (null == m_defaultInstance) && 
476                    !Modifier.isStatic(m.getModifiers()) ) {
477              if (transformer.getDebug()) {            
478                transformer.getTraceManager().fireExtensionEvent(
479                        new ExtensionEvent(transformer, m_classObj));
480                try {
481                  m_defaultInstance = m_classObj.newInstance();
482                } catch (Exception e) {
483                  throw e;
484                } finally {
485                  transformer.getTraceManager().fireExtensionEndEvent(
486                        new ExtensionEvent(transformer, m_classObj));
487                }
488              } else 
489                m_defaultInstance = m_classObj.newInstance();
490            }
491          }
492          catch (Exception e)
493          {
494            // e.printStackTrace ();
495            throw new TransformerException (e.getMessage (), e);
496          }
497          putToCache(methodKey, null, null, m);
498        }
499    
500        XSLProcessorContext xpc = new XSLProcessorContext(transformer, 
501                                                          stylesheetTree);
502    
503        try
504        {
505          if (transformer.getDebug()) {
506            transformer.getTraceManager().fireExtensionEvent(m, m_defaultInstance, 
507                    new Object[] {xpc, element});
508            try {
509              result = m.invoke(m_defaultInstance, new Object[] {xpc, element});
510            } catch (Exception e) {
511              throw e;
512            } finally {
513              transformer.getTraceManager().fireExtensionEndEvent(m, 
514                    m_defaultInstance, new Object[] {xpc, element});
515            }
516          } else                  
517            result = m.invoke(m_defaultInstance, new Object[] {xpc, element});
518        }
519        catch (InvocationTargetException e)
520        {
521          Throwable targetException = e.getTargetException();
522          
523          if (targetException instanceof TransformerException)
524            throw (TransformerException)targetException;
525          else if (targetException != null)
526            throw new TransformerException (targetException.getMessage (), 
527                    targetException);
528          else
529            throw new TransformerException (e.getMessage (), e);
530        }
531        catch (Exception e)
532        {
533          // e.printStackTrace ();
534          throw new TransformerException (e.getMessage (), e);
535        }
536    
537        if (result != null)
538        {
539          xpc.outputToResultTree (stylesheetTree, result);
540        }
541     
542      }
543     
544    }