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