/** * Copyright (C) 2007 - 2016 52°North Initiative for Geospatial Open Source * Software GmbH * * Licensed 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.n52.wps.algorithm.annotation; import java.lang.reflect.AccessibleObject; 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.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javassist.ClassClassPath; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import org.n52.wps.algorithm.annotation.AnnotationBinding.ExecuteMethodBinding; import org.n52.wps.algorithm.annotation.AnnotationBinding.InputBinding; import org.n52.wps.algorithm.annotation.AnnotationBinding.OutputBinding; import org.n52.wps.algorithm.annotation.AnnotationParser.ComplexDataInputFieldAnnotationParser; import org.n52.wps.algorithm.annotation.AnnotationParser.ComplexDataInputMethodAnnotationParser; import org.n52.wps.algorithm.annotation.AnnotationParser.ComplexDataOutputFieldAnnotationParser; import org.n52.wps.algorithm.annotation.AnnotationParser.ComplexDataOutputMethodAnnotationParser; import org.n52.wps.algorithm.annotation.AnnotationParser.ExecuteAnnotationParser; import org.n52.wps.algorithm.annotation.AnnotationParser.InputAnnotationParser; import org.n52.wps.algorithm.annotation.AnnotationParser.LiteralDataInputFieldAnnotationParser; import org.n52.wps.algorithm.annotation.AnnotationParser.LiteralDataInputMethodAnnotationParser; import org.n52.wps.algorithm.annotation.AnnotationParser.LiteralDataOutputFieldAnnotationParser; import org.n52.wps.algorithm.annotation.AnnotationParser.LiteralDataOutputMethodAnnotationParser; import org.n52.wps.algorithm.annotation.AnnotationParser.OutputAnnotationParser; import org.n52.wps.algorithm.descriptor.AlgorithmDescriptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author tkunicki */ public class AnnotatedAlgorithmIntrospector { private final static Logger LOGGER = LoggerFactory.getLogger(AnnotatedAlgorithmIntrospector.class); private final static List> INPUT_FIELD_PARSERS; private final static List> INPUT_METHOD_PARSERS; private final static List> OUTPUT_FIELD_PARSERS; private final static List> OUTPUT_METHOD_PARSERS; private final static ExecuteAnnotationParser PROCESS_PARSER; static { List> inputFieldParsers = new ArrayList>(); inputFieldParsers.add(new LiteralDataInputFieldAnnotationParser()); inputFieldParsers.add(new ComplexDataInputFieldAnnotationParser()); INPUT_FIELD_PARSERS = Collections.unmodifiableList(inputFieldParsers); List> inputMethodParsers = new ArrayList>(); inputMethodParsers.add(new LiteralDataInputMethodAnnotationParser()); inputMethodParsers.add(new ComplexDataInputMethodAnnotationParser()); INPUT_METHOD_PARSERS = Collections.unmodifiableList(inputMethodParsers); List> outputFieldParsers = new ArrayList>(); outputFieldParsers.add(new LiteralDataOutputFieldAnnotationParser()); outputFieldParsers.add(new ComplexDataOutputFieldAnnotationParser()); OUTPUT_FIELD_PARSERS = Collections.unmodifiableList(outputFieldParsers); List> outputMethodParsers = new ArrayList>(); outputMethodParsers.add(new LiteralDataOutputMethodAnnotationParser()); outputMethodParsers.add(new ComplexDataOutputMethodAnnotationParser()); OUTPUT_METHOD_PARSERS = Collections.unmodifiableList(outputMethodParsers); PROCESS_PARSER = new ExecuteAnnotationParser(); } private final static Map, AnnotatedAlgorithmIntrospector> INTROSPECTOR_MAP = new HashMap, AnnotatedAlgorithmIntrospector>(); public static synchronized AnnotatedAlgorithmIntrospector getInstrospector(Class algorithmClass) { AnnotatedAlgorithmIntrospector introspector = INTROSPECTOR_MAP.get(algorithmClass); if (introspector == null) { introspector = new AnnotatedAlgorithmIntrospector(algorithmClass); INTROSPECTOR_MAP.put(algorithmClass, introspector); } return introspector; } private Class algorithmClass; private AlgorithmDescriptor algorithmDescriptor; private ExecuteMethodBinding executeMethodBinding; private Map> inputBindingMap; private Map> outputBindingMap; public AnnotatedAlgorithmIntrospector(Class algorithmClass) { this.algorithmClass = algorithmClass; inputBindingMap = new LinkedHashMap>(); outputBindingMap = new LinkedHashMap>(); parseClass(); inputBindingMap = Collections.unmodifiableMap(inputBindingMap); outputBindingMap = Collections.unmodifiableMap(outputBindingMap); } //Modified by Gianpaolo Coro private void parseClass() { if (!algorithmClass.isAnnotationPresent(Algorithm.class)) { throw new RuntimeException("Class isn't annotated with an Algorithm annotation"); } boolean validContructor = false; try { Constructor defaultConstructor = algorithmClass.getConstructor(new Class[0]); validContructor = (defaultConstructor.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC; } catch (NoSuchMethodException ex) { // inherit error message on fall through... } catch (SecurityException ex) { throw new RuntimeException("Current security policy limits use of reflection, error introspecting " + algorithmClass.getName()); } if (!validContructor) { throw new RuntimeException("Classes with Algorithm annotation require public no-arg constructor, error introspecting " + algorithmClass.getName()); } AlgorithmDescriptor.Builder algorithmBuilder = null; Algorithm algorithm = algorithmClass.getAnnotation(Algorithm.class); //MODIFICATIONS BY G CORO TO ACCOUNT FOR METHODS ORDER List sortedMethods = new ArrayList(); List methodsIdxs = new ArrayList(); try{ for (Method m:algorithmClass.getDeclaredMethods()){ //System.out.println("Javaassist Analysing method "+m.getName()); ClassPool pool = ClassPool.getDefault(); ClassClassPath ccpath = new ClassClassPath(m.getDeclaringClass()); pool.insertClassPath(ccpath); //System.out.println("Javaassist searching for canonical name "+m.getDeclaringClass().getCanonicalName()); CtClass cc = pool.get(m.getDeclaringClass().getCanonicalName()); //System.out.println("Javaassist cc "+cc.getName()); CtMethod javassistMethod = cc.getDeclaredMethod(m.getName()); //System.out.println("Javaassist method "+javassistMethod.getName()); int linenumber = javassistMethod.getMethodInfo().getLineNumber(0); //System.out.println("Javaassist line number "+linenumber); int i=0; for (Integer methodsIdx :methodsIdxs){ if (methodsIdx>linenumber){ break; } i++; } sortedMethods.add(i,m); methodsIdxs.add(i,linenumber); } }catch(Exception e){ LOGGER.warn("error getting method order",e); } Method[] sMethods = sortedMethods.toArray(new Method[sortedMethods.size()]); algorithmBuilder = AlgorithmDescriptor.builder( algorithm.identifier().length() > 0 ? algorithm.identifier() : algorithmClass.getCanonicalName()); algorithmBuilder. title(algorithm.title()). abstrakt(algorithm.abstrakt()). version(algorithm.version()). storeSupported(algorithm.storeSupported()). statusSupported(algorithm.statusSupported()); parseElements(sMethods, INPUT_METHOD_PARSERS, OUTPUT_METHOD_PARSERS); parseElements(algorithmClass.getDeclaredFields(), INPUT_FIELD_PARSERS, OUTPUT_FIELD_PARSERS); for (Method method : sMethods) { if (method.isAnnotationPresent(PROCESS_PARSER.getSupportedAnnotation())) { ExecuteMethodBinding executeMethodBinding = PROCESS_PARSER.parse(method); if (executeMethodBinding != null) { if (this.executeMethodBinding != null) { // we need to error out here because ordering of getDeclaredMethods() or // getMethods() is not guarenteed to be consistent, if it were consistent // maybe we could ignore this state, but having an algorithm behave // differently betweeen runtimes would be bad... throw new RuntimeException("Multiple execute method bindings encountered for class " + getClass().getCanonicalName()); } this.executeMethodBinding = executeMethodBinding; } } } if(this.executeMethodBinding == null) { throw new RuntimeException("No execute method binding for class " + this.algorithmClass.getCanonicalName()); } for (InputBinding inputBinding : inputBindingMap.values()) { algorithmBuilder.addInputDescriptor(inputBinding.getDescriptor()); } for (OutputBinding outputBinding : outputBindingMap.values()) { algorithmBuilder.addOutputDescriptor(outputBinding.getDescriptor()); } algorithmDescriptor = algorithmBuilder.build(); } public AlgorithmDescriptor getAlgorithmDescriptor() { return algorithmDescriptor; } public ExecuteMethodBinding getExecuteMethodBinding() { return executeMethodBinding; } public Map> getInputBindingMap() { return inputBindingMap; } public Map> getOutputBindingMap() { return outputBindingMap; } public void parseElements( M members[], List> inputParser, List> outputParser) { for (M member : members) { for (OutputAnnotationParser parser : outputParser) { if (member.isAnnotationPresent(parser.getSupportedAnnotation())) { OutputBinding binding = parser.parse(member); if (binding != null) { outputBindingMap.put(binding.getDescriptor().getIdentifier(), binding); } } } for (InputAnnotationParser parser : inputParser) { if (member.isAnnotationPresent(parser.getSupportedAnnotation())) { InputBinding binding = parser.parse(member); if (binding != null) { inputBindingMap.put(binding.getDescriptor().getIdentifier(), binding); } } } } } }