/** * 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.InvocationTargetException; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.WildcardType; import java.util.ArrayList; import java.util.List; import org.n52.wps.algorithm.descriptor.BoundDescriptor; import org.n52.wps.algorithm.descriptor.InputDescriptor; import org.n52.wps.algorithm.descriptor.OutputDescriptor; import org.n52.wps.algorithm.util.ClassUtil; import org.n52.wps.io.data.IData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author tkunicki */ public abstract class AnnotationBinding<M extends AccessibleObject & Member> { private final static Logger LOGGER = LoggerFactory.getLogger(AnnotationBinding.class); private M member; public AnnotationBinding(M member) { this.member = member; } public M getMember() { return member; } protected boolean checkModifier() { return (getMember().getModifiers() & Modifier.PUBLIC) != 0; } public abstract boolean validate(); public static class ExecuteMethodBinding extends AnnotationBinding<Method> { public ExecuteMethodBinding(Method method) { super(method); } @Override public boolean validate() { if (!checkModifier()) { LOGGER.error("Method {} with Execute annotation can't be used, not public.", getMember()); return false; } // eh, do we really need to care about this? if (!getMember().getReturnType().equals(void.class)) { LOGGER.error("Method {} with Execute annotation can't be used, return type not void", getMember()); return false; } if (getMember().getParameterTypes().length != 0) { LOGGER.error("Method {} with Execute annotation can't be used, method parameter count is > 0.", getMember()); return false; } return true; } public void execute(Object annotatedInstance) { try { getMember().invoke(annotatedInstance); }catch (IllegalAccessException ex) { throw new RuntimeException("Internal error executing process", ex); } catch (IllegalArgumentException ex) { throw new RuntimeException("Internal error executing process", ex); } catch (InvocationTargetException ex) { Throwable cause = ex.getCause() == null ? ex : ex.getCause(); throw new RuntimeException(cause.getMessage(), cause); } } } public static abstract class DataBinding<M extends AccessibleObject & Member, D extends BoundDescriptor> extends AnnotationBinding<M> { private D descriptor; public DataBinding(M member) { super(member); } public void setDescriptor(D descriptor) { this.descriptor = descriptor; } public D getDescriptor() { return descriptor; } public abstract Type getMemberType(); public Type getType() { return getMemberType(); } public Type getPayloadType() { Type type = getType(); if (isTypeEnum()) { return String.class; } if (type instanceof Class<?>) { Class<?> inputClass = (Class<?>) type; if (inputClass.isPrimitive()) { return ClassUtil.wrap(inputClass); } } return type; } public boolean isTypeEnum() { Type inputType = getType(); return (inputType instanceof Class<?>) && ((Class<?>) inputType).isEnum(); } } public static abstract class InputBinding<M extends AccessibleObject & Member, D extends InputDescriptor> extends DataBinding<M,D> { public InputBinding(M member) { super(member); } @Override public Type getType() { Type memberType = getMemberType(); Type inputType = memberType; if (memberType instanceof Class<?>) { Class<?> memberClass = (Class<?>) memberType; if (List.class.isAssignableFrom(memberClass)) { // We treat List as List<? extends Object> inputType = NOT_PARAMETERIZED_TYPE; } } else if (memberType instanceof ParameterizedType) { ParameterizedType parameterizedMemberType = (ParameterizedType) memberType; Class<?> rawClass = (Class<?>) parameterizedMemberType.getRawType(); if (List.class.isAssignableFrom(rawClass)) { inputType = parameterizedMemberType.getActualTypeArguments()[0]; } } else { LOGGER.error("Unable to infer concrete type information for " + getMember()); } return inputType; } public boolean isMemberTypeList() { Type memberType = getMemberType(); if (memberType instanceof Class<?>) { return List.class.isAssignableFrom((Class<?>) memberType); } else if (memberType instanceof ParameterizedType) { Class<?> rawClass = (Class<?>) ((ParameterizedType) memberType).getRawType(); return List.class.isAssignableFrom(rawClass); } else { LOGGER.error("Unable to infer concrete type information for " + getMember()); } return false; } protected boolean checkType() { Type inputPayloadType = getPayloadType(); Class<? extends IData> bindingClass = getDescriptor().getBinding(); try { Class<?> bindingPayloadClass = bindingClass.getMethod("getPayload", (Class<?>[]) null).getReturnType(); if (inputPayloadType instanceof Class<?>) { return ((Class<?>) inputPayloadType).isAssignableFrom(bindingPayloadClass); } else if (inputPayloadType instanceof ParameterizedType) { // i.e. List<FeatureCollection<SimpleFeatureType,SimpleFeature>> return ((Class<?>) ((ParameterizedType) inputPayloadType).getRawType()).isAssignableFrom(bindingPayloadClass); } else if (inputPayloadType instanceof WildcardType) { // i.e. List<? extends String> or List<? super String> WildcardType inputTypeWildcardType = (WildcardType) inputPayloadType; Type[] lowerBounds = inputTypeWildcardType.getLowerBounds(); Type[] upperBounds = inputTypeWildcardType.getUpperBounds(); Class<?> lowerBoundClass = null; Class<?> upperBoundClass = null; if (lowerBounds != null && lowerBounds.length > 0) { if (lowerBounds[0] instanceof Class<?>) { lowerBoundClass = (Class<?>) lowerBounds[0]; } else if (lowerBounds[0] instanceof ParameterizedType) { lowerBoundClass = (Class<?>) ((ParameterizedType) lowerBounds[0]).getRawType(); } } if (upperBounds != null && upperBounds.length > 0) { if (upperBounds[0] instanceof Class<?>) { upperBoundClass = (Class<?>) upperBounds[0]; } else if (upperBounds[0] instanceof ParameterizedType) { upperBoundClass = (Class<?>) ((ParameterizedType) upperBounds[0]).getRawType(); } } return (upperBoundClass == null || upperBoundClass.isAssignableFrom(bindingPayloadClass)) && (lowerBounds == null || bindingPayloadClass.isAssignableFrom(lowerBoundClass)); } else { LOGGER.error("Unable to infer assignability from type for " + getMember()); } } catch (NoSuchMethodException e) { return false; } return false; } public Object unbindInput(List<IData> boundValueList) { Object value = null; if (boundValueList != null && boundValueList.size() > 0) { if (isMemberTypeList()) { List valueList = new ArrayList(boundValueList.size()); for (IData bound : boundValueList) { value = bound.getPayload(); if (isTypeEnum()) { value = Enum.valueOf((Class<? extends Enum>)getType(), (String)value); } valueList.add(value); } value = valueList; } else if (boundValueList.size() == 1) { value = boundValueList.get(0).getPayload(); if (isTypeEnum()) { value = Enum.valueOf((Class<? extends Enum>)getType(), (String)value); } } } return value; } public abstract void set(Object annotatedObject, List<IData> boundInputList); } public static abstract class OutputBinding<M extends AccessibleObject & Member, D extends OutputDescriptor> extends DataBinding<M,D> { private Constructor<? extends IData> bindingConstructor; public OutputBinding(M member) { super(member); } protected boolean checkType( ) { return getConstructor() != null; } public IData bindOutputValue(Object outputValue) { try { if (isTypeEnum()) { outputValue = ((Enum<?>)outputValue).name(); } return getConstructor().newInstance(outputValue); } catch (InstantiationException ex) { throw new RuntimeException("Internal error processing outputs", ex); } catch (SecurityException ex) { throw new RuntimeException("Internal error processing outputs", ex); } catch (IllegalAccessException ex) { throw new RuntimeException("Internal error processing outputs", ex); } catch (InvocationTargetException ex) { Throwable cause = ex.getCause() == null ? ex : ex.getCause(); throw new RuntimeException(cause.getMessage(), cause); } } public abstract IData get(Object annotatedInstance); private synchronized Constructor<? extends IData> getConstructor() { if (bindingConstructor == null ){ try { Class<? extends IData> bindingClass = getDescriptor().getBinding(); Class<?> outputPayloadClass = bindingClass.getMethod("getPayload", (Class<?>[]) null).getReturnType(); Type bindingPayloadType = getPayloadType(); if (bindingPayloadType instanceof Class<?>) { Class<?> bindingPayloadClass = (Class<?>) bindingPayloadType; if (bindingPayloadClass.isAssignableFrom(outputPayloadClass)) { bindingConstructor = bindingClass.getConstructor(bindingPayloadClass); } } } catch (NoSuchMethodException e) { // error handling on fall-through } } return bindingConstructor; } } public static class InputFieldBinding<D extends InputDescriptor> extends InputBinding<Field, D> { public InputFieldBinding(Field field) { super(field); } @Override public Type getMemberType() { return getMember().getGenericType(); } @Override public boolean validate() { if (!checkModifier()) { LOGGER.error("Field {} with input annotation can't be used, not public.", getMember()); return false; } if (!(getDescriptor().getMaxOccurs().intValue() < 2 || isMemberTypeList())) { LOGGER.error("Field {} with input annotation can't be used, maxOccurs > 1 and field is not of type List", getMember()); return false; } if (!checkType()) { LOGGER.error("Field {} with input annotation can't be used, unable to safely assign field using binding payload type", getMember()); return false; } return true; } @Override public void set(Object annotatedObject, List<IData> boundInputList) { try { getMember().set(annotatedObject, unbindInput(boundInputList)); } catch (IllegalArgumentException ex) { throw new RuntimeException("Internal error processing inputs", ex); } catch (IllegalAccessException ex) { throw new RuntimeException("Internal error processing inputs", ex); } } } public static class InputMethodBinding<D extends InputDescriptor> extends InputBinding<Method, D> { public InputMethodBinding(Method method) { super(method); } @Override public Type getMemberType() { Type[] genericParameterTypes = getMember().getGenericParameterTypes(); return (genericParameterTypes.length == 0) ? Void.class : genericParameterTypes[0]; } @Override public boolean validate() { if (!checkModifier()) { LOGGER.error("Field {} with input annotation can't be used, not public.", getMember()); return false; } if (!(getDescriptor().getMaxOccurs().intValue() < 2 || isMemberTypeList())) { LOGGER.error("Field {} with input annotation can't be used, maxOccurs > 1 and field is not of type List", getMember()); return false; } if (!checkType()) { LOGGER.error("Field {} with input annotation can't be used, unable to safely assign field using binding payload type", getMember()); return false; } return true; } @Override public void set(Object annotatedObject, List<IData> boundInputList) { try { getMember().invoke(annotatedObject, unbindInput(boundInputList)); } catch (IllegalAccessException ex) { throw new RuntimeException("Internal error processing inputs", ex); } catch (IllegalArgumentException ex) { throw new RuntimeException("Internal error processing inputs", ex); } catch (InvocationTargetException ex) { Throwable cause = ex.getCause() == null ? ex : ex.getCause(); throw new RuntimeException(cause.getMessage(), cause); } } } public static class OutputFieldBinding<D extends OutputDescriptor> extends OutputBinding<Field, D> { public OutputFieldBinding(Field field) { super(field); } @Override public Type getMemberType() { return getMember().getGenericType(); } @Override public boolean validate() { if (!checkModifier()) { LOGGER.error("Field {} with output annotation can't be used, not public.", getMember()); return false; } if (!checkType()) { LOGGER.error("Field {} with output annotation can't be used, unable to safely construct binding using field type", getMember()); return false; } return true; } @Override public IData get(Object annotatedInstance) { Object value; try { value = getMember().get(annotatedInstance); } catch (IllegalArgumentException ex) { throw new RuntimeException("Internal error processing inputs", ex); } catch (IllegalAccessException ex) { throw new RuntimeException("Internal error processing inputs", ex); } return value == null ? null : bindOutputValue(value); } } public static class OutputMethodBinding<D extends OutputDescriptor> extends OutputBinding<Method, D> { public OutputMethodBinding(Method method) { super(method); } @Override public Type getMemberType() { return getMember().getGenericReturnType(); } @Override public boolean validate() { Method method = getMember(); if (method.getParameterTypes().length != 0) { LOGGER.error("Method {} with output annotation can't be used, parameter count != 0", getMember()); return false; } if (!checkModifier()) { LOGGER.error("Method {} with output annotation can't be used, not public", getMember()); return false; } if (!checkType()) { LOGGER.error("Method {} with output annotation can't be used, unable to safely construct binding using method return type", getMember()); return false; } return true; } @Override public IData get(Object annotatedInstance) { Object value; try { value = getMember().invoke(annotatedInstance); } catch (IllegalAccessException ex) { throw new RuntimeException("Internal error processing inputs", ex); } catch (IllegalArgumentException ex) { throw new RuntimeException("Internal error processing inputs", ex); } catch (InvocationTargetException ex) { Throwable cause = ex.getCause() == null ? ex : ex.getCause(); throw new RuntimeException(cause.getMessage(), cause); } return value == null ? null : bindOutputValue(value); } } // for example, a type reprecenting the <? extends Object> for types of List<? extends Object> or List public final Type NOT_PARAMETERIZED_TYPE = new WildcardType() { @Override public Type[] getUpperBounds() { return new Type[]{Object.class}; } @Override public Type[] getLowerBounds() { return new Type[0]; } }; }