/*
* (C) Copyright 2006-2013 Nuxeo SA (http://nuxeo.com/) and others.
*
* 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.
*
* Contributors:
* bstefanescu
*/
package org.nuxeo.ecm.automation.core.impl;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.automation.OperationContext;
import org.nuxeo.ecm.automation.OperationException;
import org.nuxeo.ecm.automation.OperationType;
import org.nuxeo.ecm.automation.core.annotations.OperationMethod;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.Blobs;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentModelList;
/**
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
public class InvokableMethod implements Comparable<InvokableMethod> {
protected static final Log log = LogFactory.getLog(InvokableMethod.class);
public static final int VOID_PRIORITY = 1;
public static final int ADAPTABLE_PRIORITY = 2;
public static final int ISTANCE_OF_PRIORITY = 3;
public static final int EXACT_MATCH_PRIORITY = 4;
// priorities from 1 to 16 are reserved for internal use.
public static final int USER_PRIORITY = 16;
protected OperationType op;
protected Method method;
protected Class<?> produce;
protected Class<?> consume;
protected int priority;
public InvokableMethod(OperationType op, Method method, OperationMethod anno) {
produce = method.getReturnType();
Class<?>[] p = method.getParameterTypes();
if (p.length > 1) {
throw new IllegalArgumentException("Operation method must accept at most one argument: " + method);
}
// if produce is Void => a control operation
// if (produce == Void.TYPE) {
// throw new IllegalArgumentException("Operation method must return a
// value: "+method);
// }
this.op = op;
this.method = method;
priority = anno.priority();
if (priority > 0) {
priority += USER_PRIORITY;
}
consume = p.length == 0 ? Void.TYPE : p[0];
}
public InvokableMethod(OperationType op, Method method) {
produce = method.getReturnType();
Class<?>[] p = method.getParameterTypes();
if (p.length > 1) {
throw new IllegalArgumentException("Operation method must accept at most one argument: " + method);
}
this.op = op;
this.method = method;
if (priority > 0) {
priority += USER_PRIORITY;
}
String inputType = this.op.getInputType();
if (inputType != null) {
switch (inputType) {
case "document":
consume = DocumentModel.class;
break;
case "documents":
consume = DocumentModelList.class;
break;
case "blob":
consume = Blob.class;
break;
case "blobs":
consume = Blobs.class;
break;
default:
consume = Object.class;
break;
}
} else {
consume = p.length == 0 ? Void.TYPE : p[0];
} }
public boolean isIterable() {
return false;
}
public int getPriority() {
return priority;
}
public OperationType getOperation() {
return op;
}
public final Class<?> getOutputType() {
return produce;
}
public final Class<?> getInputType() {
return consume;
}
/**
* Return 0 for no match.
*/
public int inputMatch(Class<?> in) {
if (consume == in) {
return priority > 0 ? priority : EXACT_MATCH_PRIORITY;
}
if (consume.isAssignableFrom(in)) {
return priority > 0 ? priority : ISTANCE_OF_PRIORITY;
}
if (op.getService().isTypeAdaptable(in, consume)) {
return priority > 0 ? priority : ADAPTABLE_PRIORITY;
}
if (consume == Void.TYPE) {
return priority > 0 ? priority : VOID_PRIORITY;
}
return 0;
}
protected Object doInvoke(OperationContext ctx, Map<String, Object> args) throws OperationException,
ReflectiveOperationException {
Object target = op.newInstance(ctx, args);
Object input = ctx.getInput();
if (consume == Void.TYPE) {
// preserve last output for void methods
Object out = method.invoke(target);
return produce == Void.TYPE ? input : out;
}
if (input == null || !consume.isAssignableFrom(input.getClass())) {
// try to adapt
input = op.getService().getAdaptedValue(ctx, input, consume);
}
return method.invoke(target, input);
}
public Object invoke(OperationContext ctx, Map<String, Object> args) throws OperationException {
try {
return doInvoke(ctx, args);
} catch (OperationException e) {
throw e;
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof OperationException) {
throw (OperationException) t;
} else {
String exceptionMessage = "Failed to invoke operation " + op.getId();
if (op.getAliases() != null && op.getAliases().length > 0) {
exceptionMessage += " with aliases " + Arrays.toString(op.getAliases());
}
throw new OperationException(exceptionMessage, t);
}
} catch (ReflectiveOperationException e) {
String exceptionMessage = "Failed to invoke operation " + op.getId();
if (op.getAliases() != null && op.getAliases().length > 0) {
exceptionMessage += " with aliases " + Arrays.toString(op.getAliases());
}
throw new OperationException(exceptionMessage, e);
}
}
@Override
public String toString() {
return getClass().getSimpleName() + "(" + method + ", " + priority + ")";
}
@Override
// used for methods of the same class, so ignore the class
public int compareTo(InvokableMethod o) {
// compare on name
int cmp = method.getName().compareTo(o.method.getName());
if (cmp != 0) {
return cmp;
}
// same name, compare on parameter types
Class<?>[] pt = method.getParameterTypes();
Class<?>[] opt = o.method.getParameterTypes();
// smaller length first
cmp = pt.length - opt.length;
if (cmp != 0) {
return cmp;
}
// compare parameter classes lexicographically
for (int i = 0; i < pt.length; i++) {
cmp = pt[i].getName().compareTo(opt[i].getName());
if (cmp != 0) {
return cmp;
}
}
return 0;
}
public Method getMethod() {
return method;
}
public Class<?> getProduce() {
return produce;
}
public Class<?> getConsume() {
return consume;
}
}