/* * 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.implementation.java.introspect.impl; import static org.apache.tuscany.sca.implementation.java.introspect.JavaIntrospectionHelper.getAllInterfaces; import static org.apache.tuscany.sca.implementation.java.introspect.JavaIntrospectionHelper.getAllPublicAndProtectedFields; import static org.apache.tuscany.sca.implementation.java.introspect.JavaIntrospectionHelper.getAllUniquePublicProtectedMethods; import static org.apache.tuscany.sca.implementation.java.introspect.JavaIntrospectionHelper.getBaseType; import static org.apache.tuscany.sca.implementation.java.introspect.JavaIntrospectionHelper.toPropertyName; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jws.WebService; import org.apache.tuscany.sca.assembly.AssemblyFactory; import org.apache.tuscany.sca.assembly.Contract; import org.apache.tuscany.sca.assembly.Multiplicity; import org.apache.tuscany.sca.core.ExtensionPointRegistry; import org.apache.tuscany.sca.implementation.java.IntrospectionException; import org.apache.tuscany.sca.implementation.java.JavaConstructorImpl; import org.apache.tuscany.sca.implementation.java.JavaElementImpl; import org.apache.tuscany.sca.implementation.java.JavaImplementation; import org.apache.tuscany.sca.implementation.java.JavaParameterImpl; import org.apache.tuscany.sca.implementation.java.introspect.BaseJavaClassVisitor; import org.apache.tuscany.sca.implementation.java.introspect.JavaIntrospectionHelper; import org.apache.tuscany.sca.interfacedef.Interface; import org.apache.tuscany.sca.interfacedef.InvalidInterfaceException; import org.apache.tuscany.sca.interfacedef.java.JavaInterface; import org.apache.tuscany.sca.interfacedef.java.JavaInterfaceContract; import org.apache.tuscany.sca.interfacedef.java.JavaInterfaceFactory; import org.oasisopen.sca.ServiceReference; import org.oasisopen.sca.annotation.Callback; import org.oasisopen.sca.annotation.ComponentName; import org.oasisopen.sca.annotation.Context; import org.oasisopen.sca.annotation.Property; import org.oasisopen.sca.annotation.Reference; import org.oasisopen.sca.annotation.Remotable; /** * Heuristically evaluates an un-annotated Java implementation type to determine * services, references, and properties according to the algorithm described in * the SCA Java Client and Implementation Model Specification <p/> TODO * Implement: <p/> When no service interface is annotated, need to calculate a * single service comprising all public methods that are not reference or * property injection sites. If that service can be exactly mapped to an * interface implemented by the class then the service interface will be defined * in terms of that interface. * * @version $Rev$ $Date$ */ public class HeuristicPojoProcessor extends BaseJavaClassVisitor { public HeuristicPojoProcessor(AssemblyFactory assemblyFactory, JavaInterfaceFactory javaFactory) { super(assemblyFactory); this.javaInterfaceFactory = javaFactory; } public HeuristicPojoProcessor(ExtensionPointRegistry registry) { super(registry); } @Override public <T> void visitEnd(Class<T> clazz, JavaImplementation type) throws IntrospectionException { List<org.apache.tuscany.sca.assembly.Service> services = type.getServices(); if (services.isEmpty()) { // heuristically determine the service /** * The following is quoted from Java Specification 1.2.1.3. Introspecting services offered by a Java implementation * In the cases described below, the services offered by a Java implementation class may be determined * through introspection, eliding the need to specify them using @Service. The following algorithm is used * to determine how services are introspected from an implementation class: * * If the interfaces of the SCA services are not specified with the @Service annotation on the * implementation class, it is assumed that all implemented interfaces that have been annotated * as @Remotable are the service interfaces provided by the component. If none of the implemented * interfaces is remotable, then by default the implementation offers a single service whose type * is the implementation class. */ Set<Class<?>> interfaces = getAllInterfaces(clazz); for (Class<?> i : interfaces) { if (i.isAnnotationPresent(Remotable.class) || i.isAnnotationPresent(WebService.class)) { addService(type, i); } } if (services.isEmpty()) { // class is the interface addService(type, clazz); } } if (!(type.getReferenceMembers().isEmpty() && type.getPropertyMembers().isEmpty())) { // references and properties have been explicitly defined // if (type.getServices().isEmpty()) { // calculateServiceInterface(clazz, type, methods); // if (type.getServices().isEmpty()) { // throw new ServiceTypeNotFoundException(clazz.getName()); // } // } evaluateConstructor(type, clazz); return; } Set<Method> methods = getAllUniquePublicProtectedMethods(clazz, false); calcPropRefs(methods, services, type, clazz); evaluateConstructor(type, clazz); } // Check if the implementation is implementation.java private boolean isImplementationJava(JavaImplementation type) { return JavaImplementation.TYPE.equals(type.getType()); } private void addService(JavaImplementation type, Class<?> clazz) throws IntrospectionException { try { org.apache.tuscany.sca.assembly.Service service = createService(clazz); type.getServices().add(service); } catch (InvalidInterfaceException e) { throw new IntrospectionException(e); } } private static boolean isAnnotatedWithSCA(AnnotatedElement element) { for (Annotation a : element.getAnnotations()) { // JCI_8023 // policy annotations can be added to reference fields that // don't have @Reference annotations so we need to allow // for the fields to be detected as references if (isSCAPolicyAnnotation(a)){ continue; } else if (isSCAAnnotation(a)) { return true; } } return false; } private static boolean isSCAAnnotation(Annotation a) { return a.annotationType().getName().startsWith("org.oasisopen.sca.annotation."); } private static boolean isSCAPolicyAnnotation(Annotation a) { if (a.annotationType().getName().startsWith("org.oasisopen.sca.annotation.PolicySets") ){ return true; } else if (a.annotationType().getName().startsWith("org.oasisopen.sca.annotation.Intent") ){ return true; } else { return false; } } private <T> void calcPropRefs(Set<Method> methods, List<org.apache.tuscany.sca.assembly.Service> services, JavaImplementation type, Class<T> clazz) throws IntrospectionException { // heuristically determine the properties references // make a first pass through all public methods with one param Set<String> setters = new HashSet<String>(); Set<String> others = new HashSet<String>(); for (Method method : methods) { if (!isPublicSetter(method)) { continue; } if (isAnnotatedWithSCA(method)) { // Add the property name as others others.add(toPropertyName(method.getName())); continue; } if (!isInServiceInterface(method, services)) { // Not part of the service interface String name = toPropertyName(method.getName()); setters.add(name); // avoid duplicate property or ref names if (!type.getPropertyMembers().containsKey(name) && !type.getReferenceMembers().containsKey(name)) { Class<?> param = method.getParameterTypes()[0]; Type genericType = method.getGenericParameterTypes()[0]; if (isReferenceType(param, genericType)) { type.getReferences().add(createReference(name, param, genericType)); type.getReferenceMembers().put(name, new JavaElementImpl(method, 0)); } else { type.getProperties().add(createProperty(name, param, genericType)); type.getPropertyMembers().put(name, new JavaElementImpl(method, 0)); } } } } // second pass for protected methods with one param for (Method method : methods) { if (!isProtectedSetter(method)) { continue; } if (isAnnotatedWithSCA(method)) { // Add the property name as others others.add(toPropertyName(method.getName())); continue; } Class<?> param = method.getParameterTypes()[0]; Type paramType = method.getGenericParameterTypes()[0]; String name = toPropertyName(method.getName()); setters.add(name); // avoid duplicate property or ref names if (isReferenceType(param, method.getGenericParameterTypes()[0])) { if (!type.getReferenceMembers().containsKey(name)) { type.getReferences().add(createReference(name, param, paramType)); type.getReferenceMembers().put(name, new JavaElementImpl(method, 0)); } } else { if (!type.getPropertyMembers().containsKey(name)) { type.getProperties().add(createProperty(name, param, paramType)); type.getPropertyMembers().put(name, new JavaElementImpl(method, 0)); } } } // Public or protected fields unless there is a public or protected // setter method // for the same name Set<Field> fields = getAllPublicAndProtectedFields(clazz, false); for (Field field : fields) { if (isAnnotatedWithSCA(field)) { continue; } if (setters.contains(field.getName()) || others.contains(field.getName())) { continue; } String name = field.getName(); Class<?> paramType = field.getType(); Type genericType = field.getGenericType(); if (isReferenceType(paramType, field.getGenericType())) { if (!type.getReferenceMembers().containsKey(name)) { type.getReferences().add(createReference(name, paramType, genericType)); type.getReferenceMembers().put(name, new JavaElementImpl(field)); } } else { if (!type.getPropertyMembers().containsKey(name)) { type.getProperties().add(createProperty(name, paramType, genericType)); type.getPropertyMembers().put(name, new JavaElementImpl(field)); } } } } /** * Determines the constructor to use based on the component type's * references and properties * * @param type the component type * @param clazz the implementation class corresponding to the component type * @throws NoConstructorException if no suitable constructor is found * @throws AmbiguousConstructorException if the parameters of a constructor * cannot be unambiguously mapped to references and properties */ @SuppressWarnings("unchecked") private <T> void evaluateConstructor(JavaImplementation type, Class<T> clazz) throws IntrospectionException { // determine constructor if one is not annotated JavaConstructorImpl<?> definition = type.getConstructor(); Constructor constructor; boolean explict = false; if (definition != null && definition.getConstructor() .isAnnotationPresent(org.oasisopen.sca.annotation.Constructor.class)) { // the constructor was already defined explicitly return; } else if (definition != null) { explict = true; constructor = definition.getConstructor(); } else { if (!isImplementationJava(type)) { // FIXME: [rfeng] Don't process the constructors for non implementation.java types return; } // no definition, heuristically determine constructor Constructor[] constructors = clazz.getConstructors(); if (constructors.length == 0) { throw new NoConstructorException("[JCI50001] No public constructor for class"); } else if (constructors.length == 1) { // Only one constructor, take it constructor = constructors[0]; } else { Constructor<T> selected = null; for (Constructor<T> ctor : constructors) { if (allArgsAnnotated(ctor)) { selected = ctor; for (Constructor<T> ctor2 : constructors) { if (selected != ctor2 && allArgsAnnotated(ctor2)) { throw new InvalidConstructorException("[JCI50005] Multiple annotated constructors"); } } } } if (selected == null) { for (Constructor<T> ctor : constructors) { if (ctor.getParameterTypes().length == 0) { selected = ctor; break; } } } if (selected == null) { throw new NoConstructorException(); } constructor = selected; definition = type.getConstructors().get(selected); type.setConstructor(definition); } definition = type.getConstructors().get(constructor); type.setConstructor(definition); } JavaParameterImpl[] parameters = definition.getParameters(); if (parameters.length == 0) { return; } Map<String, JavaElementImpl> props = type.getPropertyMembers(); Map<String, JavaElementImpl> refs = type.getReferenceMembers(); Annotation[][] annotations = constructor.getParameterAnnotations(); if (!explict) { // the constructor wasn't defined by an annotation, so check to see // if any of the params have an annotation // which we can impute as explicitly defining the constructor, e.g. // @Property, @Reference, or @Autowire explict = injectionAnnotationsPresent(annotations); } if (explict) { for (int i = 0; i < parameters.length; i++) { if (isAnnotated(parameters[i])) { continue; } else if (!findReferenceOrProperty(parameters[i], props, refs)) { throw new AmbiguousConstructorException(parameters[i].toString()); } } } else { if (!areUnique(parameters)) { throw new AmbiguousConstructorException("Cannot resolve non-unique parameter types, use @Constructor"); } if (!calcPropRefUniqueness(props.values(), refs.values())) { throw new AmbiguousConstructorException("Cannot resolve non-unique parameter types, use @Constructor"); } if (!(props.isEmpty() && refs.isEmpty())) { calcParamNames(parameters, props, refs); } else { heuristicParamNames(type, parameters); } } } private boolean allArgsAnnotated(Constructor<?> ctor) { if (ctor.getParameterTypes().length < 1) { return false; } for (Annotation[] as : ctor.getParameterAnnotations()) { if (as.length < 1) { return false; } } return true; } private void calcParamNames(JavaParameterImpl[] parameters, Map<String, JavaElementImpl> props, Map<String, JavaElementImpl> refs) throws AmbiguousConstructorException { // the constructor param types must unambiguously match defined // reference or property types for (JavaParameterImpl param : parameters) { if (!findReferenceOrProperty(param, props, refs)) { throw new AmbiguousConstructorException(param.getName()); } } } private void heuristicParamNames(JavaImplementation type, JavaParameterImpl[] parameters) throws IntrospectionException { // heuristically determine refs and props from the parameter types for (JavaParameterImpl p : parameters) { String name = p.getType().getSimpleName().toLowerCase(); if (isReferenceType(p.getType(), p.getGenericType())) { type.getReferences().add(createReference(name, p.getType(), p.getGenericType())); p.setClassifer(Reference.class); type.getReferenceMembers().put(name, p); } else { type.getProperties().add(createProperty(name, p.getType(), p.getGenericType())); p.setClassifer(Property.class); type.getPropertyMembers().put(name, p); } p.setName(name); } } /** * Returns true if the union of the given collections of properties and * references have unique Java types */ private boolean calcPropRefUniqueness(Collection<JavaElementImpl> props, Collection<JavaElementImpl> refs) { Class<?>[] classes = new Class[props.size() + refs.size()]; int i = 0; for (JavaElementImpl property : props) { classes[i] = property.getType(); i++; } for (JavaElementImpl reference : refs) { classes[i] = reference.getType(); i++; } return areUnique(classes); } /** * Unambiguously finds the reference or property associated with the given * type * * @return the name of the reference or property if found, null if not * @throws AmbiguousConstructorException if the constructor parameter cannot * be resolved to a property or reference */ private boolean findReferenceOrProperty(JavaParameterImpl parameter, Map<String, JavaElementImpl> props, Map<String, JavaElementImpl> refs) throws AmbiguousConstructorException { boolean found = false; if (!"".equals(parameter.getName())) { // Match by name JavaElementImpl prop = props.get(parameter.getName()); if (prop != null && prop.getType() == parameter.getType()) { parameter.setClassifer(Property.class); return true; } JavaElementImpl ref = refs.get(parameter.getName()); if (ref != null && ref.getType() == parameter.getType()) { parameter.setClassifer(Reference.class); return true; } } for (JavaElementImpl property : props.values()) { if (property.getType() == parameter.getType()) { if (found) { throw new AmbiguousConstructorException("Ambiguous property or reference for constructor type", (Member)parameter.getAnchor()); } parameter.setClassifer(Property.class); parameter.setName(property.getName()); found = true; // do not break since ambiguities must be checked, i.e. more // than one prop or ref of the same type } } for (JavaElementImpl reference : refs.values()) { if (reference.getType() == parameter.getType()) { if (found) { throw new AmbiguousConstructorException("Ambiguous property or reference for constructor type", (Member)parameter.getAnchor()); } parameter.setClassifer(Reference.class); parameter.setName(reference.getName()); found = true; // do not break since ambiguities must be checked, i.e. more // than one prop or ref of the same type } } return found; } /** * Creates a mapped property. * * @param name the property name * @param paramType the property type */ private org.apache.tuscany.sca.assembly.Property createProperty(String name, Class<?> javaClass, Type genericType) { return AbstractPropertyProcessor.createProperty(assemblyFactory, name, javaClass, genericType); } private org.apache.tuscany.sca.assembly.Reference createReference(String name, Class<?> paramType, Type genericType ) throws IntrospectionException { org.apache.tuscany.sca.assembly.Reference reference = assemblyFactory.createReference(); reference.setName(name); JavaInterfaceContract interfaceContract = javaInterfaceFactory.createJavaInterfaceContract(); reference.setInterfaceContract(interfaceContract); // Compute the interface type dealing with array/Collection types and with Generic types Class<?> baseType = getBaseType(paramType, genericType); if (ServiceReference.class.isAssignableFrom(baseType)) { if (Collection.class.isAssignableFrom(paramType)) { genericType = JavaIntrospectionHelper.getParameterType(genericType); } baseType = JavaIntrospectionHelper.getBusinessInterface(baseType, genericType); } // end if try { JavaInterface callInterface = javaInterfaceFactory.createJavaInterface(baseType); reference.getInterfaceContract().setInterface(callInterface); if (callInterface.getCallbackClass() != null) { JavaInterface callbackInterface = javaInterfaceFactory.createJavaInterface(callInterface.getCallbackClass()); reference.getInterfaceContract().setCallbackInterface(callbackInterface); } // Multiplicity of an implicit reference is 1..1 for a simple interface type // and 1..n for an array of interface or Collection of interface type // as defined in the OASIS SCA Java POJO specification, section 8.1 : TUSCANY-3636 if( isCollectionType( paramType ) || isArrayType( paramType )) { reference.setMultiplicity(Multiplicity.ONE_N); } else { reference.setMultiplicity(Multiplicity.ONE_ONE); } // end if } catch (InvalidInterfaceException e1) { throw new IntrospectionException(e1); } // end try // FIXME: This part seems to have already been taken care above!! try { processCallback(paramType, reference); } catch (InvalidServiceTypeException e) { throw new IntrospectionException(e); } return reference; } /** * Reports if a parameter type is a form of java.util.Collection * @param paramType * @return true if paramType is a form of java.util.Collection, false otherwise */ private boolean isCollectionType( Class<?> paramType ) { if( Collection.class.isAssignableFrom(paramType) ) return true; return false; } /** * Reports if a parameter type is an array * @param paramType * @return true if paramType is an array */ private boolean isArrayType( Class<?> paramType ) { if( paramType.isArray() ) return true; return false; } private org.apache.tuscany.sca.assembly.Service createService(Class<?> interfaze) throws InvalidInterfaceException { org.apache.tuscany.sca.assembly.Service service = assemblyFactory.createService(); service.setName(interfaze.getSimpleName()); JavaInterfaceContract interfaceContract = javaInterfaceFactory.createJavaInterfaceContract(); service.setInterfaceContract(interfaceContract); JavaInterface callInterface = javaInterfaceFactory.createJavaInterface(interfaze); service.getInterfaceContract().setInterface(callInterface); if (callInterface.getCallbackClass() != null) { JavaInterface callbackInterface = javaInterfaceFactory.createJavaInterface(callInterface.getCallbackClass()); service.getInterfaceContract().setCallbackInterface(callbackInterface); } Interface javaInterface = service.getInterfaceContract().getInterface(); javaInterface.setRemotable(interfaze.getAnnotation(Remotable.class) != null); service.getInterfaceContract().setInterface(javaInterface); return service; } private void processCallback(Class<?> interfaze, Contract contract) throws InvalidServiceTypeException { Callback callback = interfaze.getAnnotation(Callback.class); if (callback != null && !Void.class.equals(callback.value())) { Class<?> callbackClass = callback.value(); JavaInterface javaInterface; try { javaInterface = javaInterfaceFactory.createJavaInterface(callbackClass); contract.getInterfaceContract().setCallbackInterface(javaInterface); } catch (InvalidInterfaceException e) { throw new InvalidServiceTypeException("Invalid callback interface "+callbackClass, interfaze); } } else if (callback != null && Void.class.equals(callback.value())) { throw new InvalidServiceTypeException("No callback interface specified on annotation", interfaze); } } /** * Utility methods */ /** * Verify if the method is a public setter * @param method * @return */ private static boolean isPublicSetter(Method method) { return method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers()) && method.getName().startsWith("set") && method.getReturnType() == void.class; } /** * Verify if the method is a protected setter * @param method * @return */ private static boolean isProtectedSetter(Method method) { return method.getParameterTypes().length == 1 && Modifier.isProtected(method.getModifiers()) && method.getName().startsWith("set") && method.getReturnType() == void.class; } /** * @param collection * @return */ private static boolean areUnique(Class<?>[] collection) { Set<Class<?>> set = new HashSet<Class<?>>(Arrays.asList(collection)); return set.size() == collection.length; } /** * Returns true if a given type is reference according to the SCA * specification rules for determining reference types The following rules * are used to determine whether an unannotated field or setter method is a * property or reference: * <ol> * <li>If its type is simple, then it is a property. * <li>If its type is complex, then if the type is an interface marked by * * @Remotable, then it is a reference; otherwise, it is a property. * <li>Otherwise, if the type associated with the member is an * array or a java.util.Collection, the basetype is the element * type of the array or the parameterized type of the * Collection; otherwise the basetype is the member type. If the * basetype is an interface with an * @Remotable or * @Service annotation then the member is defined as a reference. Otherwise, * it is defined as a property. * </ol> * <p> * The name of the reference or of the property is derived from the * name found on the setter method or on the field. */ // FIXME: [rfeng] What if it's a collection of references? private static boolean isReferenceType(Class<?> cls, Type genericType) { Class<?> baseType = JavaIntrospectionHelper.getBaseType(cls, genericType); return baseType.isInterface() && baseType.isAnnotationPresent(Remotable.class); } /** * Returns true if the given operation is defined in the collection of * service interfaces * @param operation * @param services * @return */ private static boolean isInServiceInterface(Method operation, List<org.apache.tuscany.sca.assembly.Service> services) { for (org.apache.tuscany.sca.assembly.Service service : services) { Interface interface1 = service.getInterfaceContract().getInterface(); if (interface1 instanceof JavaInterface) { Class<?> clazz = ((JavaInterface)interface1).getJavaClass(); if (isMethodMatched(clazz, operation)) { return true; } } } return false; } /** * Test if the class declares a method which matches the signature of the * given method * * @param clazz * @param method * @return */ private static boolean isMethodMatched(Class<?> clazz, Method method) { if (method.getDeclaringClass() == clazz) { return true; } Method[] methods = clazz.getMethods(); for (Method m : methods) { if (JavaIntrospectionHelper.exactMethodMatch(method, m)) { return true; } } return false; } /** * Verify if there is any SCA annotation on the parameter * @param parameter * @return */ private static boolean isAnnotated(JavaParameterImpl parameter) { for (Annotation annotation : parameter.getAnnotations()) { if (isSCAAnnotation(annotation)) { return true; } } return false; } /** * Verify if the parameters are unique * @param parameters * @return */ private static boolean areUnique(JavaParameterImpl[] parameters) { Set<Class<?>> set = new HashSet<Class<?>>(parameters.length); for (JavaParameterImpl p : parameters) { if (!set.add(p.getType())) { return false; } } return true; } /** * Verify if the annotations are SCA annotation * @param annots * @return */ private static boolean injectionAnnotationsPresent(Annotation[][] annots) { for (Annotation[] annotations : annots) { for (Annotation annotation : annotations) { Class<? extends Annotation> annotType = annotation.annotationType(); if (annotType == Property.class || annotType == Reference.class || annotType == Resource.class || annotType == ComponentName.class || annotType == Context.class || annotType == Callback.class) { return true; } } } return false; } }