package org.jibeframework.core.service.impl;
import groovy.lang.GroovyObject;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jibeframework.core.Context;
import org.jibeframework.core.JibeRuntimeException;
import org.jibeframework.core.annotation.UIComponent;
import org.jibeframework.core.annotation.UIController;
import org.jibeframework.core.app.ApplicationInitializedListener;
import org.jibeframework.core.app.config.ConfigMap;
import org.jibeframework.core.app.method.ArgumentsResolver;
import org.jibeframework.core.app.method.BootstrapedMethodNotFoundException;
import org.jibeframework.core.app.method.MethodHolder;
import org.jibeframework.core.service.ConfigService;
import org.jibeframework.core.service.MethodService;
import org.jibeframework.core.ui.Event;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
@Service("jibe.MethodService")
public class MethodServiceImpl implements MethodService, ApplicationInitializedListener, ApplicationContextAware {
public Map<Class<? extends Annotation>, Map<String, String>> holders = new HashMap<Class<? extends Annotation>, Map<String, String>>();
@Autowired
private ArgumentsResolver argumentsResolver;
@Autowired
private ConfigService configService;
public ApplicationContext applicationContext;
private List<String> annotationClasses = new ArrayList<String>();
private static final Log logger = LogFactory.getLog(MethodHolder.class);
public boolean annotatedMethodExists(Class<? extends Annotation> annotation, String id) {
return holders.get(annotation).containsKey(id);
}
public Object invoke(Class<? extends Annotation> annotationClass, String methodId, Object... args) {
MethodHolder holder = resolveAnnotatedMethodHolder(annotationClass, methodId, args);
Object retValue = holder.invoke();
return retValue;
}
public Object invoke(String value, Object... args) {
MethodHolder holder = resolveHolder(value, args);
Object retValue = holder.invoke();
return retValue;
}
public Collection<String> getAnnotatedMethodNames(Class<? extends Annotation> annotationClass) {
return holders.get(annotationClass).keySet();
}
public Object invokeServiceMethod(String methodId, Object... args) {
String beanName = StringUtils.substringBeforeLast(methodId, ".");
String methodName = StringUtils.substringAfterLast(methodId, ".");
Object bean = applicationContext.getBean(beanName);
List<Method> candidates = new ArrayList<Method>();
Method[] methods = bean.getClass().getMethods();
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals(methodName)) {
candidates.add(methods[i]);
}
}
if (candidates.isEmpty()) {
throw new JibeRuntimeException("Method " + methodId + " could not be found");
}
if (candidates.size() == 1) {
Method method = candidates.get(0);
return ReflectionUtils.invokeMethod(method, bean, args);
} else {
throw new JibeRuntimeException("Overloaded methods are not supported yet");
}
}
private MethodHolder resolveAnnotatedMethodHolder(Class<? extends Annotation> annotationClass, String methodId,
Object... args) {
if (holders.containsKey(annotationClass)) {
if (holders.get(annotationClass).containsKey(methodId)) {
String methodSignature = holders.get(annotationClass).get(methodId);
return resolveHolder(methodSignature, args);
}
}
throw new BootstrapedMethodNotFoundException("Boostraped method not found: " + methodId);
}
public MethodHolder resolveHolder(String methodId, Object... args) {
List<MethodHolder> candidates = new ArrayList<MethodHolder>();
String beanName = StringUtils.substringBeforeLast(methodId, ".");
String methodName = StringUtils.substringAfterLast(methodId, ".");
Class<?> userClass = null;
Context context = Context.getCurrentContext();
Object bean = null;
if (context.containsViewBean(beanName)) {
bean = context.getViewBean(beanName);
userClass = ((GroovyObject) bean).getMetaClass().getTheClass();
} else {
bean = applicationContext.getBean(beanName);
userClass = ClassUtils.getUserClass(bean.getClass());
}
if (AnnotationUtils.findAnnotation(userClass, Controller.class) != null
|| AnnotationUtils.findAnnotation(userClass, UIComponent.class) != null
|| AnnotationUtils.findAnnotation(userClass, UIController.class) != null) {
Method[] methods = userClass.getMethods();
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals(methodName)) {
Object[] resolvedArguments = argumentsResolver.resolveArguments(methods[i], args);
candidates.add(new MethodHolder(methodId, bean, methods[i], resolvedArguments));
}
}
if (candidates.isEmpty()) {
throw new JibeRuntimeException("Method " + methodId + " could not be found");
}
Double bestGuess = null;
MethodHolder bestCandidate = null;
for (MethodHolder candidate : candidates) {
double guess = candidate.getQuality();
if (bestGuess == null || guess > bestGuess) {
bestGuess = guess;
bestCandidate = candidate;
}
}
return bestCandidate;
}
throw new JibeRuntimeException(
"It is only possible to invoke methods on classes annotated with @Controller or @UIComponent annotations: "
+ methodId);
}
public void setAnnotationClasses(List<String> annotationClasses) {
this.annotationClasses = annotationClasses;
}
public void onApplicationInitialized() {
List<String> beanNames = Arrays.asList(applicationContext.getBeanDefinitionNames());
for (String beanName : beanNames) {
Object bean = null;
try {
bean = applicationContext.getBean(beanName);
} catch (Exception e) {
continue;
}
Controller controllerAnn = bean.getClass().getAnnotation(Controller.class);
if (controllerAnn != null) {
bootstrapObject(beanName, bean, annotationClasses);
}
}
}
@SuppressWarnings("unchecked")
private void bootstrapObject(String beanName, Object bean, List<String> classes) {
Method[] methods = ClassUtils.getUserClass(bean.getClass()).getMethods();
for (String clazzName : classes) {
for (Method method : methods) {
Class<? extends Annotation> clazz;
try {
clazz = (Class<? extends Annotation>) Class.forName(clazzName);
} catch (ClassNotFoundException e1) {
throw new JibeRuntimeException("Annotation class could not be found: " + clazzName);
}
Annotation annotation = method.getAnnotation(clazz);
if (annotation != null) {
String id = null;
try {
id = (String) annotation.getClass().getMethod("value").invoke(annotation);
} catch (Exception e) {
throw new JibeRuntimeException("Annotation does not have value property: "
+ annotation.annotationType().getName());
}
if (StringUtils.isEmpty(id)) {
id = String.format("%1$s.%2$s", beanName, method.getName());
}
String methodName = String.format("%1$s.%2$s", beanName, method.getName());
if (!holders.containsKey(clazz)) {
holders.put(clazz, new HashMap<String, String>());
}
holders.get(clazz).put(id, methodName);
}
}
}
}
@SuppressWarnings("unchecked")
public void fireEvent(Object source, String id, String event, Object... data) {
ConfigMap handlers = (ConfigMap) configService.getConfig("core.binding");
Event eventData = new Event(source, data);
if (handlers.containsKey(id)) {
Map map = (Map) handlers.get(id);
if (map.containsKey(event)) {
invoke((String) map.get(event), eventData);
}
}
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}