52n-wps-algorithm-gcube/src/main/java/org/n52/wps/algorithm/annotation/AnnotationBinding.java

477 lines
19 KiB
Java
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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]; }
};
}