package org.jboss.seam.remoting;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import org.jboss.seam.remoting.annotations.WebRemote;
import org.jboss.seam.remoting.util.Strings;
import org.jboss.seam.remoting.wrapper.ConversionException;
import org.jboss.seam.remoting.wrapper.ConversionScore;
import org.jboss.seam.remoting.wrapper.Wrapper;
/**
* @author Shane Bryzak
*/
public class Call {
public static Annotation[] EMPTY_ANNOTATIONS = new Annotation[]{};
private BeanManager beanManager;
private String methodName;
private Throwable exception;
private List<Wrapper> params = new ArrayList<Wrapper>();
private Object result;
private CallContext context;
private List<String> constraints = null;
private Bean<?> targetBean = null;
public Call(BeanManager beanManager, String beanName, String qualifiers, String methodName) {
this.beanManager = beanManager;
this.methodName = methodName;
this.context = new CallContext(beanManager);
Set<Bean<?>> beans = beanManager.getBeans(beanName);
if (beans.isEmpty()) {
try {
Class<?> beanType = Class.forName(beanName);
Annotation[] q = qualifiers != null && !Strings.isEmpty(qualifiers) ?
new AnnotationsParser(beanType, qualifiers, beanManager).getAnnotations() :
EMPTY_ANNOTATIONS;
beans = beanManager.getBeans(beanType, q);
} catch (ClassNotFoundException ex) {
throw new IllegalArgumentException("Invalid bean class specified: " + beanName);
}
if (beans.isEmpty()) {
throw new IllegalArgumentException(
"Could not find bean with bean with type/name " + beanName +
", qualifiers [" + qualifiers + "]");
}
}
targetBean = beans.iterator().next();
}
/**
* Return the call context.
*
* @return CallContext
*/
public CallContext getContext() {
return context;
}
/**
* Returns the exception thrown by the invoked method. If no exception was
* thrown, will return null.
*/
public Throwable getException() {
return exception;
}
/**
* Add a parameter to this call.
*
* @param param
*/
public void addParameter(Wrapper param) {
params.add(param);
}
/**
* Returns the result of this call.
*
* @return Wrapper
*/
public Object getResult() {
return result;
}
/**
* Required for unit tests
*
* @param result
*/
public void setResult(Object result) {
this.result = result;
}
/**
* Returns the object graph constraints annotated on the method that is
* called.
*
* @return List The constraints
*/
public List<String> getConstraints() {
return constraints;
}
/**
* Required for unit tests
*
* @param constraints
*/
public void setConstraints(List<String> constraints) {
this.constraints = constraints;
}
/**
* Execute this call
*
* @throws Exception
*/
public void execute() throws Exception {
CreationalContext<?> ctx = beanManager.createCreationalContext(targetBean);
// Get an instance of the component
Object instance = beanManager.getReference(targetBean, targetBean.getBeanClass(), ctx);
;
if (instance == null) {
throw new RuntimeException(String.format(
"Could not create instance of bean %s",
targetBean.getBeanClass().getName()));
}
// Find the method according to the method name and the parameter classes
Method m = findMethod(methodName, targetBean.getBeanClass());
if (m == null) {
throw new RuntimeException("No compatible method found.");
}
if (m.getAnnotation(WebRemote.class).exclude().length > 0) {
constraints = Arrays.asList(m.getAnnotation(WebRemote.class).exclude());
}
Object[] params = convertParams(m.getGenericParameterTypes());
try {
result = m.invoke(instance, params);
} catch (InvocationTargetException e) {
this.exception = e.getCause();
}
}
/**
* Convert our parameter values to an Object array of the specified target
* types.
*
* @param targetTypes Class[] An array containing the target class types.
* @return Object[] The converted parameter values.
*/
private Object[] convertParams(Type[] targetTypes)
throws ConversionException {
Object[] paramValues = new Object[targetTypes.length];
for (int i = 0; i < targetTypes.length; i++) {
paramValues[i] = params.get(i).convert(targetTypes[i]);
}
return paramValues;
}
/**
* Find the best matching method within the specified class according to the
* parameter types that were provided to the Call.
*
* @param name String The name of the method.
* @param cls Class The Class to search in.
* @return Method The best matching method.
*/
private Method findMethod(String name, Class<?> cls) {
Map<Method, Integer> candidates = new HashMap<Method, Integer>();
for (Method m : cls.getDeclaredMethods()) {
if (m.getAnnotation(WebRemote.class) == null)
continue;
if (name.equals(m.getName())
&& m.getParameterTypes().length == params.size()) {
int score = 0;
for (int i = 0; i < m.getParameterTypes().length; i++) {
ConversionScore convScore = params.get(i).conversionScore(
m.getParameterTypes()[i]);
if (convScore == ConversionScore.nomatch)
continue;
score += convScore.getScore();
}
candidates.put(m, score);
}
}
Method bestMethod = null;
int bestScore = 0;
for (Entry<Method, Integer> entry : candidates.entrySet()) {
int thisScore = entry.getValue();
if (bestMethod == null || thisScore > bestScore) {
bestMethod = entry.getKey();
bestScore = thisScore;
}
}
return bestMethod;
}
}