package org.apache.ode.spi.di;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Qualifier;
import javax.xml.namespace.QName;
import org.apache.ode.spi.di.OperationAnnotationScanner.OperationModel;
import org.apache.ode.spi.work.ExecutionUnit.InBuffer;
import org.apache.ode.spi.work.ExecutionUnit.OutBuffer;
import org.apache.ode.spi.work.ExecutionUnit.WorkItem;
import org.apache.ode.spi.work.Operation;
import org.apache.ode.spi.work.Operation.I;
import org.apache.ode.spi.work.Operation.IO;
import org.apache.ode.spi.work.Operation.IOP;
import org.apache.ode.spi.work.Operation.IP;
import org.apache.ode.spi.work.Operation.O;
import org.apache.ode.spi.work.Operation.OP;
import org.apache.ode.spi.work.Operation.OperationSet;
public class OperationAnnotationScanner implements AnnotationScanner<Map<QName, OperationModel>> {
protected static final Logger log = Logger.getLogger(OperationAnnotationScanner.class.getName());
public Map<QName, OperationModel> scan(Class<?> clazz) {
log.fine(String.format("scanned class %s\n", clazz));
if (clazz.isAnnotationPresent(OperationSet.class)) {
OperationSet opset = clazz.getAnnotation(OperationSet.class);
Map<QName, OperationModel> operations = new HashMap<>();
for (Method m : clazz.getMethods()) {
if (m.isAnnotationPresent(Operation.class)) {
if (!Modifier.isStatic(m.getModifiers())) {
log.severe(String.format("Operation in Class %s method %s, is not static, skipping", clazz.getName(), m.getName()));
continue;
}
Operation op = m.getAnnotation(Operation.class);
String operationNamespace = op.namespace();
if (operationNamespace.length() == 0) {
operationNamespace = opset.namespace();
}
if (operationNamespace.length() == 0) {
log.severe(String.format("Unable to determine operation namespace for Class %s method %s, skipping", clazz.getName(), m.getName()));
continue;
}
String operationName = op.name();
if (operationName.length() == 0) {
operationName = m.getName();
}
QName operationQName = new QName(operationNamespace, operationName);
OperationModel om = new OperationModel(operationQName);
if (op.command().name().length() > 0) {
String commandNamespace = op.command().namespace();
if (commandNamespace.length() == 0) {
commandNamespace = opset.commandNamespace();
}
if (commandNamespace.length() == 0) {
log.severe(String.format("Unable to determine command namespace for Class %s method %s, skipping", clazz.getName(), m.getName()));
continue;
}
om.commandName = new QName(commandNamespace, op.command().name());
}
try {
om.handle = MethodHandles.lookup().unreflect(m);
} catch (IllegalAccessException iae) {
log.log(Level.SEVERE, "", iae);
continue;
}
if (inspect(clazz, true, m, om)) {
operations.put(operationQName, om);
}
}
}
return operations;
} else {
return null;
}
}
@Qualifier
@Retention(RUNTIME)
@Target(FIELD)
public @interface Operations {
}
public static class BaseModel {
protected ParameterInfo[] parameterInfo;
protected int inputCount = 0;
protected int outputCount = 0;
protected boolean hasReturn = false;
protected Class<InBuffer> inBuffer = null;
protected Class<OutBuffer> outBuffer = null;
public ParameterInfo[] parameterInfo() {
return parameterInfo;
}
public int inputCount() {
return inputCount;
}
public int outputCount() {
return outputCount;
}
public boolean hasReturn() {
return hasReturn;
}
public Class<InBuffer> inBuffer() {
return inBuffer;
}
public Class<OutBuffer> outBuffer() {
return outBuffer;
}
}
public static class OperationModel extends BaseModel {
private final QName operationName;
private QName commandName;
private MethodHandle handle;
public OperationModel(QName operationName) {
this.operationName = operationName;
}
public QName commandName() {
return commandName;
}
public QName operationName() {
return operationName;
}
public MethodHandle handle() {
return handle;
}
}
public static interface ParameterInfo {
}
public static class IOBase implements ParameterInfo {
public final int index;
public final boolean inject;
public final Class<?> paramType;
public final Annotation qualifier;
public IOBase(int index, Class<?> paramType, boolean inject, Annotation qualifier) {
this.index = index;
this.paramType = paramType;
this.inject = inject;
this.qualifier = qualifier;
}
}
public static class WorkItemInput implements ParameterInfo {
}
public static class Input extends IOBase {
public Input(int index, Class<?> paramType, boolean inject, Annotation qualifier) {
super(index, paramType, inject, qualifier);
}
}
public static class BufferInput implements ParameterInfo {
public final Class<? extends InBuffer> buffer;
public BufferInput(Class<? extends InBuffer> buffer) {
this.buffer = buffer;
}
}
public static class Output extends IOBase {
public Output(int index, Class<?> paramType, boolean inject, Annotation qualifier) {
super(index, paramType, inject, qualifier);
}
}
public static class InputOutput extends IOBase {
public final int outIndex;
public InputOutput(int inIndex, int outIndex, Class<?> paramType, boolean inject, Annotation qualifier) {
super(inIndex, paramType, inject, qualifier);
this.outIndex = outIndex;
}
}
public static class BufferOutput implements ParameterInfo {
public final Class<? extends OutBuffer> buffer;
public BufferOutput(Class<? extends OutBuffer> buffer) {
this.buffer = buffer;
}
}
public static boolean inspect(Class<?> clazz, boolean injectionAllowed, Method m, BaseModel om) {
if (m.getReturnType() != void.class) {
om.hasReturn = true;
}
List<ParameterInfo> params = new ArrayList<>(m.getParameterTypes().length);
Annotation[][] paramAnnotations = m.getParameterAnnotations();
Class[] parameters = m.getParameterTypes();
for (int i = 0; i < parameters.length; i++) {
if (paramAnnotations[i].length == 0) {
if (InBuffer.class.isAssignableFrom(parameters[i])) {
om.inBuffer = parameters[i];
om.inputCount = parameters[i].getFields().length;
params.add(new BufferInput(parameters[i]));
continue;
} else if (OutBuffer.class.isAssignableFrom(parameters[i])) {
om.outBuffer = parameters[i];
om.outputCount = parameters[i].getFields().length;
params.add(new BufferOutput(parameters[i]));
continue;
} else if (WorkItem.class.isAssignableFrom(parameters[i])) {
params.add(new WorkItemInput());
continue;
} else {
log.severe(String.format("Parameter for Class %s method %s parameter %d, is missing mandatory annotation, skipping", clazz.getName(), m.getName(), i));
return false;
}
}
Annotation a = paramAnnotations[i][0];
if (a instanceof I || a instanceof IP) {
if (om.inBuffer != null) {
log.severe(String.format("Parameter for Class %s method %s parameter %d, is input but method has InBuffer, skipping", clazz.getName(), m.getName(), i));
return false;
}
if (a instanceof IP) {
if (!injectionAllowed) {
log.severe(String.format("Parameter for Class %s method %s parameter %d, requesting injection but is not allowed, skipping", clazz.getName(), m.getName(),
i));
return false;
}
if (paramAnnotations[i].length == 2 && paramAnnotations[i][1].annotationType().isAnnotationPresent(Qualifier.class)) {
params.add(new Input(om.inputCount++, parameters[i], true, paramAnnotations[i][1]));
} else {
params.add(new Input(om.inputCount++, parameters[i], true, null));
}
} else {
params.add(new Input(om.inputCount++, parameters[i], false, null));
}
} else if (a instanceof O || a instanceof OP) {
if (om.hasReturn) {
log.severe(String.format("Parameter for Class %s method %s parameter %d, is output but method has return value, skipping", clazz.getName(), m.getName(), i));
return false;
}
if (om.outBuffer != null) {
log.severe(String.format("Parameter for Class %s method %s parameter %d, is output but method has OutBuffer, skipping", clazz.getName(), m.getName(), i));
return false;
}
if (a instanceof OP) {
if (!injectionAllowed) {
log.severe(String.format("Parameter for Class %s method %s parameter %d, requesting injection but is not allowed, skipping", clazz.getName(), m.getName(),
i));
return false;
}
if (paramAnnotations[i].length == 2 && paramAnnotations[i][1].annotationType().isAnnotationPresent(Qualifier.class)) {
params.add(new Output(om.outputCount++, parameters[i], true, paramAnnotations[i][1]));
} else {
params.add(new Output(om.outputCount++, parameters[i], true, null));
}
} else {
params.add(new Output(om.outputCount++, parameters[i], false, null));
}
} else if (a instanceof IO || a instanceof IOP) {
if (om.inBuffer != null) {
log.severe(String.format("Parameter for Class %s method %s parameter %d, is inputoutput but method has InBuffer, skipping", clazz.getName(), m.getName(), i));
return false;
}
if (om.hasReturn) {
log.severe(String.format("Parameter for Class %s method %s parameter %d, is inputoutput but method has return value, skipping", clazz.getName(), m.getName(), i));
return false;
}
if (om.outBuffer != null) {
log.severe(String.format("Parameter for Class %s method %s parameter %d, is inputoutput but method has OutBuffer, skipping", clazz.getName(), m.getName(), i));
return false;
}
if (a instanceof IOP) {
if (!injectionAllowed) {
log.severe(String.format("Parameter for Class %s method %s parameter %d, requesting injection but is not allowed, skipping", clazz.getName(), m.getName(),
i));
return false;
}
if (paramAnnotations[i].length == 2 && paramAnnotations[i][1].annotationType().isAnnotationPresent(Qualifier.class)) {
params.add(new InputOutput(om.inputCount++, om.outputCount++, parameters[i], true, paramAnnotations[i][1]));
} else {
params.add(new InputOutput(om.inputCount++, om.outputCount++, parameters[i], true, null));
}
} else {
params.add(new InputOutput(om.inputCount++, om.outputCount++, parameters[i], false, null));
}
} else {
log.severe(String.format("Parameter for Class %s method %s parameter %d, is unknown, skipping", clazz.getName(), m.getName(), i));
return false;
}
}
om.parameterInfo = params.toArray(new ParameterInfo[params.size()]);
return true;
}
}