From e5b7380c874745c989d1816b8f552504f038e1bc Mon Sep 17 00:00:00 2001 From: lresende Date: Thu, 26 Sep 2013 20:33:20 +0000 Subject: 2.0 branch for possible maintenance release git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@1526672 13f79535-47bb-0310-9956-ffa450edef68 --- .../java/impl/JavaInterfaceIntrospectorImpl.java | 447 +++++++++++++++++++++ 1 file changed, 447 insertions(+) create mode 100644 sca-java-2.x/branches/2.0/modules/interface-java/src/main/java/org/apache/tuscany/sca/interfacedef/java/impl/JavaInterfaceIntrospectorImpl.java (limited to 'sca-java-2.x/branches/2.0/modules/interface-java/src/main/java/org/apache/tuscany/sca/interfacedef/java/impl/JavaInterfaceIntrospectorImpl.java') diff --git a/sca-java-2.x/branches/2.0/modules/interface-java/src/main/java/org/apache/tuscany/sca/interfacedef/java/impl/JavaInterfaceIntrospectorImpl.java b/sca-java-2.x/branches/2.0/modules/interface-java/src/main/java/org/apache/tuscany/sca/interfacedef/java/impl/JavaInterfaceIntrospectorImpl.java new file mode 100644 index 0000000000..304d39a51d --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/interface-java/src/main/java/org/apache/tuscany/sca/interfacedef/java/impl/JavaInterfaceIntrospectorImpl.java @@ -0,0 +1,447 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.interfacedef.java.impl; + +import static org.apache.tuscany.sca.interfacedef.Operation.IDL_INPUT; +import static org.apache.tuscany.sca.interfacedef.Operation.IDL_OUTPUT; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Future; + +import javax.xml.namespace.QName; +import javax.xml.ws.AsyncHandler; +import javax.xml.ws.Holder; +import javax.xml.ws.Response; + +import org.apache.tuscany.sca.interfacedef.DataType; +import org.apache.tuscany.sca.interfacedef.InvalidAnnotationException; +import org.apache.tuscany.sca.interfacedef.InvalidCallbackException; +import org.apache.tuscany.sca.interfacedef.InvalidInterfaceException; +import org.apache.tuscany.sca.interfacedef.InvalidOperationException; +import org.apache.tuscany.sca.interfacedef.Operation; +import org.apache.tuscany.sca.interfacedef.OverloadedOperationException; +import org.apache.tuscany.sca.interfacedef.ParameterMode; +import org.apache.tuscany.sca.interfacedef.impl.DataTypeImpl; +import org.apache.tuscany.sca.interfacedef.java.JavaInterface; +import org.apache.tuscany.sca.interfacedef.java.JavaInterfaceFactory; +import org.apache.tuscany.sca.interfacedef.java.JavaOperation; +import org.apache.tuscany.sca.interfacedef.java.introspect.JavaInterfaceVisitor; +import org.apache.tuscany.sca.interfacedef.util.JavaXMLMapper; +import org.apache.tuscany.sca.interfacedef.util.XMLType; +import org.oasisopen.sca.ResponseDispatch; +import org.oasisopen.sca.annotation.AsyncFault; +import org.oasisopen.sca.annotation.AsyncInvocation; +import org.oasisopen.sca.annotation.OneWay; +import org.oasisopen.sca.annotation.Remotable; + +/** + * Default implementation of a Java interface introspector. + * + * @version $Rev$ $Date$ + */ +public class JavaInterfaceIntrospectorImpl { + + private static final String UNKNOWN_DATABINDING = null; + + private JavaInterfaceFactory javaFactory = null; + private List visitors = new ArrayList(); + private boolean loadedVisitors; + + public JavaInterfaceIntrospectorImpl(JavaInterfaceFactory javaFactory) { + this.javaFactory = javaFactory; + } + + public void introspectInterface(JavaInterface javaInterface, Class clazz) throws InvalidInterfaceException { + + if(!loadedVisitors) { + this.visitors = javaFactory.getInterfaceVisitors(); + } + + javaInterface.setJavaClass(clazz); + + boolean remotable = clazz.isAnnotationPresent(Remotable.class); + + // Consider @javax.ejb.Remote, java.rmi.Remote and javax.ejb.EJBObject + // equivalent to @Remotable + if (!remotable) { + for (Annotation annotation : clazz.getAnnotations()) { + if ("javax.ejb.Remote".equals(annotation.annotationType().getName())) { + remotable = true; + break; + } + } + } + if (!remotable) { + for (Class superInterface : clazz.getInterfaces()) { + if (Remote.class == superInterface || "javax.ejb.EJBObject".equals(superInterface.getName())) { + remotable = true; + break; + } + } + } + + if (remotable) { + if (javaInterface.isRemotableSetFromSCDL() && javaInterface.isRemotable() == false) { + throw new InvalidAnnotationException("[JCA30005] @Remotable annotation present in a interface marked as not remotable in the SCDL", Remotable.class); + } + } else { + if (javaInterface.isRemotableSet()) { + remotable = javaInterface.isRemotable(); + } + } + + javaInterface.setRemotable(remotable); + + Class callbackClass = null; + org.oasisopen.sca.annotation.Callback callback = clazz.getAnnotation(org.oasisopen.sca.annotation.Callback.class); + if (callback != null && !Void.class.equals(callback.value())) { + callbackClass = callback.value(); + if (remotable && !callbackClass.isAnnotationPresent(Remotable.class)) { + throw new InvalidCallbackException("Callback " + callbackClass.getName() + + " must be remotable on remotable interface " + clazz.getName()); + } + if (!remotable && callbackClass.isAnnotationPresent(Remotable.class)) { + throw new InvalidCallbackException("Callback" + callbackClass.getName() + + " must not be remotable on local interface " + clazz.getName()); + } + } else if (callback != null && Void.class.equals(callback.value())) { + throw new InvalidCallbackException("No callback interface specified on callback annotation in " + clazz.getName()); + } + + javaInterface.setCallbackClass(callbackClass); + + String ns = JavaXMLMapper.getNamespace(clazz); + javaInterface.getOperations().addAll(getOperations(clazz, remotable, ns)); + + for (JavaInterfaceVisitor extension : visitors) { + extension.visitInterface(javaInterface); + } // end for + + // Check if any methods have disallowed annotations + // Check if any private methods have illegal annotations that should be raised as errors + checkMethodAnnotations(clazz, javaInterface); + } // end method introspectInterface + + private void checkMethodAnnotations(Class clazz, JavaInterface javaInterface) throws InvalidAnnotationException { + + final Class _clazz = clazz; + Method[] declaredMethods = (Method[])AccessController.doPrivileged(new PrivilegedAction() { + public Method[] run() { + return _clazz.getDeclaredMethods(); + } + }); + + for (final Method method : declaredMethods) { + for ( Annotation a : method.getAnnotations() ) { + if( a instanceof Remotable ) { + // [JCA90053] @Remotable annotation cannot be on a method that is not a setter method + if( !JavaIntrospectionHelper.isSetter(method) ) { + throw new InvalidAnnotationException("[JCA90053] @Remotable annotation present on an interface method" + + " which is not a Setter method: " + javaInterface.getName() + "/" + method.getName(), Remotable.class); + } // end if + } // end if + } // end for + + // Parameter annotations + for (Annotation[] parmAnnotations : method.getParameterAnnotations()) { + for (Annotation annotation : parmAnnotations) { + if (annotation instanceof Remotable ) { + throw new InvalidAnnotationException("[JCA90053] @Remotable annotation present on an interface method" + + " parameter: " + javaInterface.getName() + "/" + method.getName(), Remotable.class); + } // end if + } // end for + } // end for + } + } // end method checkMethodAnnotations + + private Class[] getActualTypes(Type[] types, Class[] rawTypes, Map typeBindings, boolean ignoreAsyncHolder) { + int x = ignoreAsyncHolder ? types.length -1 : types.length; + Class[] actualTypes = new Class[x]; + for (int i = 0; i < x; i++) { + actualTypes[i] = getActualType(types[i], rawTypes[i], typeBindings); + } + return actualTypes; + } + + private Class getActualType(Type type, Class rawType, Map typeBindings) { + if (type instanceof TypeVariable) { + TypeVariable typeVariable = (TypeVariable)type; + type = typeBindings.get(typeVariable.getName()); + if (type instanceof Class) { + return (Class)type; + } + } + return rawType; + } + + @SuppressWarnings("rawtypes") + private List getOperations(Class clazz, + boolean remotable, + String ns) throws InvalidInterfaceException { + + Set genericInterfaces = new HashSet(); + collectGenericInterfaces(clazz, genericInterfaces); + Map typeBindings = new HashMap(); + for (Type genericInterface : genericInterfaces) { + if (genericInterface instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType)genericInterface; + TypeVariable[] typeVariables = ((Class)parameterizedType.getRawType()).getTypeParameters(); + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + for (int i = 0; i < typeArguments.length; i++) { + typeBindings.put(typeVariables[i].getName(), typeArguments[i]); + } + } + } + + boolean isAsyncService = clazz.isAnnotationPresent(AsyncInvocation.class); + + Method[] methods = clazz.getMethods(); + List operations = new ArrayList(methods.length); + Set names = remotable ? new HashSet() : null; + for (Method method : methods) { + boolean hasMultipleOutputs = false; + if (method.getDeclaringClass() == Object.class) { + // Skip the methods on the Object.class + continue; + } + String name = method.getName(); + + Class lastParameter = method.getParameterTypes().length > 0 ? method.getParameterTypes()[method.getParameterTypes().length-1] : null; + boolean isAsyncMethod = isAsyncService && name.endsWith("Async") && lastParameter != null && ResponseDispatch.class.equals(lastParameter); + + if (isAsyncMethod) { + name = name.substring(0, name.length()-5); + } + + if (remotable && names.contains(name)) { + throw new OverloadedOperationException(method); + } + if (remotable && !jaxwsAsyncMethod(method)) { + names.add(name); + } + + Class[] parameterTypes = getActualTypes(method.getGenericParameterTypes(), method.getParameterTypes(), typeBindings, isAsyncMethod); + + Class returnType; + if (isAsyncMethod) { + ParameterizedType t = (ParameterizedType)method.getGenericParameterTypes()[method.getGenericParameterTypes().length-1]; + returnType = (Class)t.getActualTypeArguments()[0]; + } else { + returnType = getActualType(method.getGenericReturnType(), method.getReturnType(), typeBindings); + } + + Class[] faultTypes = + getActualTypes(method.getGenericExceptionTypes(), method.getExceptionTypes(), typeBindings, false); + Class[] allOutputTypes = getOutputTypes(returnType, parameterTypes); + + // For async server interfaces, faults are described using the @AsyncFaults annotation + if( method.isAnnotationPresent(AsyncFault.class) ) { + faultTypes = readAsyncFaultTypes( method ); + } // end if + + boolean nonBlocking = method.isAnnotationPresent(OneWay.class); + if (nonBlocking) { + if (!(returnType == void.class)) { + throw new InvalidOperationException( + "Method should return 'void' when declared with an @OneWay annotation. " + method, + method); + } + if (!(faultTypes.length == 0)) { + throw new InvalidOperationException( + "Method should not declare exceptions with an @OneWay annotation. " + method, + method); + } + } + + JavaOperation operation = new JavaOperationImpl(); + operation.setName(name); + + // Given details of Holder mapping, it's easier to handle output first. + List outputDataTypes = new ArrayList(); + XMLType xmlReturnType = new XMLType(new QName(ns, "return"), null); + DataType returnDataType = null; + if (returnType == void.class) { + operation.setReturnTypeVoid(true); + } else { + if (isAsyncMethod) { + returnDataType = new DataTypeImpl(UNKNOWN_DATABINDING, returnType, returnType, xmlReturnType); + } else { + returnDataType = new DataTypeImpl(UNKNOWN_DATABINDING, returnType, method.getGenericReturnType(), xmlReturnType); + } + operation.setReturnTypeVoid(false); + outputDataTypes.add(returnDataType); + } + + // Handle Input Types + List paramDataTypes = new ArrayList(parameterTypes.length); + Type[] genericParamTypes = method.getGenericParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + Class paramType = parameterTypes[i]; + XMLType xmlParamType = new XMLType(new QName(ns, "arg" + i), null); + + DataTypeImpl xmlDataType = new DataTypeImpl( + UNKNOWN_DATABINDING, paramType, genericParamTypes[i],xmlParamType); + ParameterMode mode = ParameterMode.IN; + // Holder pattern. Physical types of Holder classes are updated to to aid in transformations. + if ( Holder.class == paramType) { + hasMultipleOutputs = true; + Type firstActual = getFirstActualType( genericParamTypes[ i ] ); + if ( firstActual != null ) { + xmlDataType.setPhysical( (Class)firstActual ); + mode = ParameterMode.INOUT; + } + outputDataTypes.add(xmlDataType); + } + paramDataTypes.add( xmlDataType); + operation.getParameterModes().add(mode); + } + + // Fault types + List faultDataTypes = new ArrayList(faultTypes.length); + Type[] genericFaultTypes = method.getGenericExceptionTypes(); + if( method.isAnnotationPresent(AsyncFault.class) ) { + genericFaultTypes = readAsyncGenericFaultTypes( method ); + } // end if + for (int i = 0; i < faultTypes.length; i++) { + Class faultType = faultTypes[i]; + // Only add checked exceptions + // JAXWS Specification v2.1 section 3.7 says RemoteException should not be mapped + if (Exception.class.isAssignableFrom(faultType) && (!RuntimeException.class.isAssignableFrom(faultType)) + && (!RemoteException.class.isAssignableFrom(faultType))) { + XMLType xmlFaultType = new XMLType(new QName(ns, faultType.getSimpleName()), null); + DataType faultDataType = + new DataTypeImpl(UNKNOWN_DATABINDING, faultType, genericFaultTypes[i], xmlFaultType); + faultDataTypes.add(new DataTypeImpl(UNKNOWN_DATABINDING, faultType, genericFaultTypes[i], + faultDataType)); + } + } + + DataType> inputType = + new DataTypeImpl>(IDL_INPUT, Object[].class, paramDataTypes); + DataType> outputType = + new DataTypeImpl>(IDL_OUTPUT, Object[].class, outputDataTypes); + + operation.setOutputType(outputType); + + operation.setInputType(inputType); + operation.setFaultTypes(faultDataTypes); + operation.setNonBlocking(nonBlocking); + operation.setJavaMethod(method); + operation.setHasArrayWrappedOutput(hasMultipleOutputs); + operation.setAsyncServer(isAsyncMethod); + operations.add(operation); + } + return operations; + } + + + private Class[] getOutputTypes(Class returnType, Class[] parameterTypes) { + + ArrayList> returnTypes = new ArrayList>(); + returnTypes.add(returnType); + for ( Class clazz : parameterTypes ) { + if ( Holder.class == clazz ) + returnTypes.add(clazz); + } + Class[] arrayType = new Class[0]; + return returnTypes.toArray(arrayType); + } + + + + /** + * Reads the fault types declared in an @AsyncFault annotation on an async server method + * @param method - the Method + * @return - an array of fault/exception classes + */ + private Class[] readAsyncFaultTypes( Method method ) { + AsyncFault theFaults = method.getAnnotation(AsyncFault.class); + if ( theFaults == null ) return null; + return theFaults.value(); + } // end method readAsyncFaultTypes + + /** + * Reads the generic fault types declared in an @AsyncFault annotation on an async server method + * @param method - the Method + * @return - an array of fault/exception classes + */ + private Type[] readAsyncGenericFaultTypes( Method method ) { + AsyncFault theFaults = method.getAnnotation(AsyncFault.class); + if ( theFaults == null ) return null; + return theFaults.value(); + } // end method readAsyncFaultTypes + + private boolean jaxwsAsyncMethod(Method method) { + if (method.getName().endsWith("Async")) { + if (method.getReturnType().isAssignableFrom(Future.class)) { + if (method.getParameterTypes().length > 0) { + if (method.getParameterTypes()[method.getParameterTypes().length-1].isAssignableFrom(AsyncHandler.class)) { + return true; + } + } + } + if (method.getReturnType().isAssignableFrom(Response.class)) { + return true; + } + } + return false; + } + + + /** + * Given a Class, returns T, otherwise null. + * @param testClass + * @return + */ + protected static Type getFirstActualType(Type genericType) { + if (genericType instanceof ParameterizedType) { + ParameterizedType pType = (ParameterizedType)genericType; + Type[] actualTypes = pType.getActualTypeArguments(); + if ((actualTypes != null) && (actualTypes.length > 0)) { + return actualTypes[0]; + } + } + return null; + } + + private void collectGenericInterfaces(Class clazz, Set genericInterfaces) { + for (Type t : clazz.getGenericInterfaces()) { + genericInterfaces.add(t); + } + Class[] interfaces = clazz.getInterfaces(); + for(Class c : interfaces){ + collectGenericInterfaces(c, genericInterfaces); + } + } + +} -- cgit v1.2.3