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: ExtensionHandlerJavaPackage.java 1225574 2011-12-29 15:49:16Z mrglavas $
020     */
021    package org.apache.xalan.extensions;
022    
023    import java.io.IOException;
024    import java.lang.reflect.Constructor;
025    import java.lang.reflect.InvocationTargetException;
026    import java.lang.reflect.Method;
027    import java.lang.reflect.Modifier;
028    import java.util.Vector;
029    
030    import javax.xml.transform.TransformerException;
031    
032    import org.apache.xalan.res.XSLMessages;
033    import org.apache.xalan.res.XSLTErrorResources;
034    import org.apache.xalan.templates.ElemTemplateElement;
035    import org.apache.xalan.templates.Stylesheet;
036    import org.apache.xalan.trace.ExtensionEvent;
037    import org.apache.xalan.transformer.TransformerImpl;
038    import org.apache.xpath.functions.FuncExtFunction;
039    import org.apache.xpath.objects.XObject;
040    
041    /**
042     * Represents an extension namespace for XPath that handles java packages
043     * that may be fully or partially specified.
044     * It is recommended that the class URI be of one of the following forms:
045     * <pre>
046     *   xalan://partial.class.name
047     *   xalan://
048     *   http://xml.apache.org/xalan/java (which is the same as xalan://)
049     * </pre>
050     * However, we do not enforce this.  If the class name contains a
051     * a /, we only use the part to the right of the rightmost slash.
052     * In addition, we ignore any "class:" prefix.
053     * Provides functions to test a function's existence and call a function.
054     * Also provides functions to test an element's existence and call an
055     * element.
056     *
057     * @author <a href="mailto:garyp@firstech.com">Gary L Peskin</a>
058     *
059     * @xsl.usage internal
060     */
061    
062    
063    public class ExtensionHandlerJavaPackage extends ExtensionHandlerJava
064    {
065    
066      /**
067       * Construct a new extension namespace handler given all the information
068       * needed. 
069       * 
070       * @param namespaceUri the extension namespace URI that I'm implementing
071       * @param scriptLang   language of code implementing the extension
072       * @param className    the beginning of the class name of the class.  This
073       *                     should be followed by a dot (.)
074       */
075      public ExtensionHandlerJavaPackage(String namespaceUri,
076                                         String scriptLang,
077                                         String className)
078      {
079        super(namespaceUri, scriptLang, className);
080      }
081    
082    
083      /**
084       * Tests whether a certain function name is known within this namespace.
085       * Since this is for a package, we concatenate the package name used when
086       * this handler was created and the function name specified in the argument.
087       * There is
088       * no information regarding the arguments to the function call or
089       * whether the method implementing the function is a static method or
090       * an instance method.
091       * @param function name of the function being tested
092       * @return true if its known, false if not.
093       */
094    
095      public boolean isFunctionAvailable(String function) 
096      {
097        try
098        {
099          String fullName = m_className + function;
100          int lastDot = fullName.lastIndexOf('.');
101          if (lastDot >= 0)
102          {
103            Class myClass = getClassForName(fullName.substring(0, lastDot));
104            Method[] methods = myClass.getMethods();
105            int nMethods = methods.length;
106            function = fullName.substring(lastDot + 1);
107            for (int i = 0; i < nMethods; i++)
108            {
109              if (methods[i].getName().equals(function))
110                return true;
111            }
112          }
113        }
114        catch (ClassNotFoundException cnfe) {}
115    
116        return false;
117      }
118    
119    
120      /**
121       * Tests whether a certain element name is known within this namespace.
122       * Looks for a method with the appropriate name and signature.
123       * This method examines both static and instance methods.
124       * @param element name of the element being tested
125       * @return true if its known, false if not.
126       */
127    
128      public boolean isElementAvailable(String element) 
129      {
130        try
131        {
132          String fullName = m_className + element;
133          int lastDot = fullName.lastIndexOf('.');
134          if (lastDot >= 0)
135          {
136            Class myClass = getClassForName(fullName.substring(0, lastDot));
137            Method[] methods = myClass.getMethods();
138            int nMethods = methods.length;
139            element = fullName.substring(lastDot + 1);
140            for (int i = 0; i < nMethods; i++)
141            {
142              if (methods[i].getName().equals(element))
143              {
144                Class[] paramTypes = methods[i].getParameterTypes();
145                if ( (paramTypes.length == 2)
146                  && paramTypes[0].isAssignableFrom(
147                                         org.apache.xalan.extensions.XSLProcessorContext.class)
148                  && paramTypes[1].isAssignableFrom(
149                                           org.apache.xalan.templates.ElemExtensionCall.class) )
150                {
151                  return true;
152                }
153              }
154            }
155          }
156        }
157        catch (ClassNotFoundException cnfe) {}
158    
159        return false;
160      }
161    
162    
163      /**
164       * Process a call to a function in the package java namespace.
165       * There are three possible types of calls:
166       * <pre>
167       *   Constructor:
168       *     packagens:class.name.new(arg1, arg2, ...)
169       *
170       *   Static method:
171       *     packagens:class.name.method(arg1, arg2, ...)
172       *
173       *   Instance method:
174       *     packagens:method(obj, arg1, arg2, ...)
175       * </pre>
176       * We use the following rules to determine the type of call made:
177       * <ol type="1">
178       * <li>If the function name ends with a ".new", call the best constructor for
179       *     class whose name is formed by concatenating the value specified on
180       *     the namespace with the value specified in the function invocation
181       *     before ".new".</li>
182       * <li>If the function name contains a period, call the best static method "method"
183       *     in the class whose name is formed by concatenating the value specified on
184       *     the namespace with the value specified in the function invocation.</li>
185       * <li>Otherwise, call the best instance method "method"
186       *     in the class whose name is formed by concatenating the value specified on
187       *     the namespace with the value specified in the function invocation.
188       *     Note that a static method of the same
189       *     name will <i>not</i> be called in the current implementation.  This
190       *     module does not verify that the obj argument is a member of the
191       *     package namespace.</li>
192       * </ol>
193       *
194       * @param funcName Function name.
195       * @param args     The arguments of the function call.
196       * @param methodKey A key that uniquely identifies this class and method call.
197       * @param exprContext The context in which this expression is being executed.
198       * @return the return value of the function evaluation.
199       *
200       * @throws TransformerException          if parsing trouble
201       */
202    
203      public Object callFunction (String funcName, 
204                                  Vector args, 
205                                  Object methodKey,
206                                  ExpressionContext exprContext)
207        throws TransformerException 
208      {
209    
210        String className;
211        String methodName;
212        Class  classObj;
213        Object targetObject;
214        int lastDot = funcName.lastIndexOf('.');
215        Object[] methodArgs;
216        Object[][] convertedArgs;
217        Class[] paramTypes;
218    
219        try
220        {
221          TransformerImpl trans = (exprContext != null) ?
222            (TransformerImpl)exprContext.getXPathContext().getOwnerObject() : null;
223          if (funcName.endsWith(".new")) {                   // Handle constructor call
224    
225            methodArgs = new Object[args.size()];
226            convertedArgs = new Object[1][];
227            for (int i = 0; i < methodArgs.length; i++)
228            {
229              methodArgs[i] = args.get(i);
230            }
231            
232            Constructor c = (methodKey != null) ?
233              (Constructor) getFromCache(methodKey, null, methodArgs) : null;
234            
235            if (c != null)
236            {
237              try
238              {
239                paramTypes = c.getParameterTypes();
240                MethodResolver.convertParams(methodArgs, convertedArgs, paramTypes, exprContext);
241                return c.newInstance(convertedArgs[0]);
242              }
243              catch (InvocationTargetException ite)
244              {
245                throw ite;
246              }
247              catch(Exception e)
248              {
249                // Must not have been the right one
250              }
251            }
252            className = m_className + funcName.substring(0, lastDot);
253            try
254            {
255              classObj = getClassForName(className);
256            }
257            catch (ClassNotFoundException e) 
258            {
259              throw new TransformerException(e);
260            }
261            c = MethodResolver.getConstructor(classObj, 
262                                              methodArgs,
263                                              convertedArgs,
264                                              exprContext);
265            if (methodKey != null)
266              putToCache(methodKey, null, methodArgs, c);
267            
268            if (trans != null && trans.getDebug()) {
269                trans.getTraceManager().fireExtensionEvent(new ExtensionEvent(trans, c, convertedArgs[0]));            
270                Object result;
271                try {
272                    result = c.newInstance(convertedArgs[0]);
273                } catch (Exception e) {
274                    throw e;
275                } finally {
276                    trans.getTraceManager().fireExtensionEndEvent(new ExtensionEvent(trans, c, convertedArgs[0]));
277                }
278                return result;
279            } else
280                return c.newInstance(convertedArgs[0]);
281          }
282    
283          else if (-1 != lastDot) {                         // Handle static method call
284    
285            methodArgs = new Object[args.size()];
286            convertedArgs = new Object[1][];
287            for (int i = 0; i < methodArgs.length; i++)
288            {
289              methodArgs[i] = args.get(i);
290            }
291            Method m = (methodKey != null) ?
292              (Method) getFromCache(methodKey, null, methodArgs) : null;
293            
294            if (m != null && !trans.getDebug())
295            {
296              try
297              {
298                paramTypes = m.getParameterTypes();
299                MethodResolver.convertParams(methodArgs, convertedArgs, paramTypes, exprContext);
300                return m.invoke(null, convertedArgs[0]);
301              }
302              catch (InvocationTargetException ite)
303              {
304                throw ite;
305              }
306              catch(Exception e)
307              {
308                // Must not have been the right one
309              }
310            }
311            className = m_className + funcName.substring(0, lastDot);
312            methodName = funcName.substring(lastDot + 1);
313            try
314            {
315              classObj = getClassForName(className);
316            }
317            catch (ClassNotFoundException e) 
318            {
319              throw new TransformerException(e);
320            }
321            m = MethodResolver.getMethod(classObj,
322                                         methodName,
323                                         methodArgs, 
324                                         convertedArgs,
325                                         exprContext,
326                                         MethodResolver.STATIC_ONLY);
327            if (methodKey != null)
328              putToCache(methodKey, null, methodArgs, m);
329            
330            if (trans != null && trans.getDebug()) {
331                trans.getTraceManager().fireExtensionEvent(m, null, convertedArgs[0]);            
332                Object result;
333                try {
334                    result = m.invoke(null, convertedArgs[0]);
335                } catch (Exception e) {
336                    throw e;
337                } finally {
338                    trans.getTraceManager().fireExtensionEndEvent(m, null, convertedArgs[0]);
339                }
340                return result;
341            }
342            else
343                return m.invoke(null, convertedArgs[0]);
344          }
345    
346          else {                                            // Handle instance method call
347    
348            if (args.size() < 1)
349            {
350              throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_INSTANCE_MTHD_CALL_REQUIRES, new Object[]{funcName })); //"Instance method call to method " + funcName
351                                        //+ " requires an Object instance as first argument");
352            }
353            targetObject = args.get(0);
354            if (targetObject instanceof XObject)          // Next level down for XObjects
355              targetObject = ((XObject) targetObject).object();
356            methodArgs = new Object[args.size() - 1];
357            convertedArgs = new Object[1][];
358            for (int i = 0; i < methodArgs.length; i++)
359            {
360              methodArgs[i] = args.get(i+1);
361            }
362            Method m = (methodKey != null) ?
363              (Method) getFromCache(methodKey, targetObject, methodArgs) : null;
364            
365            if (m != null)
366            {
367              try
368              {
369                paramTypes = m.getParameterTypes();
370                MethodResolver.convertParams(methodArgs, convertedArgs, paramTypes, exprContext);
371                return m.invoke(targetObject, convertedArgs[0]);
372              }
373              catch (InvocationTargetException ite)
374              {
375                throw ite;
376              }
377              catch(Exception e)
378              {
379                // Must not have been the right one
380              }
381            }
382            classObj = targetObject.getClass();
383            m = MethodResolver.getMethod(classObj,
384                                         funcName,
385                                         methodArgs, 
386                                         convertedArgs,
387                                         exprContext,
388                                         MethodResolver.INSTANCE_ONLY);
389            if (methodKey != null)
390              putToCache(methodKey, targetObject, methodArgs, m);
391            
392            if (trans != null && trans.getDebug()) {
393                trans.getTraceManager().fireExtensionEvent(m, targetObject, convertedArgs[0]);            
394                Object result;
395                try {
396                    result = m.invoke(targetObject, convertedArgs[0]);
397                } catch (Exception e) {
398                    throw e;
399                } finally {
400                    trans.getTraceManager().fireExtensionEndEvent(m, targetObject, convertedArgs[0]);
401                }
402                return result;
403            } else       
404                return m.invoke(targetObject, convertedArgs[0]);
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       * For this namespace, only static element methods are currently supported.
448       * If instance methods are needed, please let us know your requirements.
449       * @param localPart      Element name's local part.
450       * @param element        The extension element being processed.
451       * @param transformer      Handle to TransformerImpl.
452       * @param stylesheetTree The compiled stylesheet tree.
453       * @param methodKey      A key that uniquely identifies this element call.
454       * @throws IOException           if loading trouble
455       * @throws TransformerException          if parsing trouble
456       */
457    
458      public void processElement (String localPart,
459                                  ElemTemplateElement element,
460                                  TransformerImpl transformer,
461                                  Stylesheet stylesheetTree,
462                                  Object methodKey)
463        throws TransformerException, IOException
464      {
465        Object result = null;
466        Class classObj;
467    
468        Method m = (Method) getFromCache(methodKey, null, null);
469        if (null == m)
470        {
471          try
472          {
473            String fullName = m_className + localPart;
474            int lastDot = fullName.lastIndexOf('.');
475            if (lastDot < 0)
476              throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_ELEMENT_NAME, new Object[]{fullName })); //"Invalid element name specified " + fullName);
477            try
478            {
479              classObj = getClassForName(fullName.substring(0, lastDot));
480            }
481            catch (ClassNotFoundException e) 
482            {
483              throw new TransformerException(e);
484            }
485            localPart = fullName.substring(lastDot + 1);
486            m = MethodResolver.getElementMethod(classObj, localPart);
487            if (!Modifier.isStatic(m.getModifiers()))
488              throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_ELEMENT_NAME_METHOD_STATIC, new Object[]{fullName })); //"Element name method must be static " + fullName);
489          }
490          catch (Exception e)
491          {
492            // e.printStackTrace ();
493            throw new TransformerException (e);
494          }
495          putToCache(methodKey, null, null, m);
496        }
497    
498        XSLProcessorContext xpc = new XSLProcessorContext(transformer, 
499                                                          stylesheetTree);
500    
501        try
502        {
503          if (transformer.getDebug()) {
504              transformer.getTraceManager().fireExtensionEvent(m, null, new Object[] {xpc, element});
505            try {
506                result = m.invoke(null, new Object[] {xpc, element});
507            } catch (Exception e) {
508                throw e;
509            } finally {            
510                transformer.getTraceManager().fireExtensionEndEvent(m, null, new Object[] {xpc, element});
511            }
512          } else
513            result = m.invoke(null, new Object[] {xpc, element});
514        }
515        catch (InvocationTargetException ite)
516        {
517          Throwable resultException = ite;
518          Throwable targetException = ite.getTargetException();
519     
520          if (targetException instanceof TransformerException)
521            throw ((TransformerException)targetException);
522          else if (targetException != null)
523            resultException = targetException;
524                
525          throw new TransformerException(resultException);
526        }
527        catch (Exception e)
528        {
529          // e.printStackTrace ();
530          throw new TransformerException (e);
531        }
532    
533        if (result != null)
534        {
535          xpc.outputToResultTree (stylesheetTree, result);
536        }
537     
538      }
539    
540    }