package com.aggrepoint.service; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javassist.CtNewMethod; import javassist.LoaderClassPath; import javassist.NotFoundException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.aggrepoint.dao.DaoService; import com.aggrepoint.dao.annotation.DefaultDao; /** * Add implementation for methods declared in DaoService interface but not in * service implementation class<br> * <br> * This class loader modifies class that implements DaoService method and has * one field with @DefaultDao annotation * * @author Jiangming Yang (yangjm@gmail.com) */ public class ServiceClassLoader extends ClassLoader { private static final Log logger = LogFactory .getLog(ServiceClassLoader.class); private ClassLoader innerLoader; Map<Class<?>, Class<?>> classMap = Collections .synchronizedMap(new HashMap<Class<?>, Class<?>>()); /** Dao fields of service class */ static HashMap<Class<?>, Field> daoFields = new HashMap<Class<?>, Field>(); public ServiceClassLoader(ClassLoader inner) { super(inner); innerLoader = inner; } public static Field findDaoField(Class<?> clz) { if (!daoFields.containsKey(clz)) { daoFields.put(clz, null); Field daoField = null; for (Field field : clz.getDeclaredFields()) { DefaultDao[] anns = field .getAnnotationsByType(DefaultDao.class); if (anns != null && anns.length > 0) { if (daoField != null) throw new IllegalArgumentException( "Mutiple @DefaultDao fields found in service class: " + clz.getName()); daoField = field; } } if (daoField != null) daoField.setAccessible(true); daoFields.put(clz, daoField); } return daoFields.get(clz); } /** * Check whether c implements interface i * * @param c * @param i * @return */ public static boolean implementsInterface(Class<?> c, Class<?> i) { if (c == i) return true; for (Class<?> x : c.getInterfaces()) if (implementsInterface(x, i)) return true; return false; } public static HashSet<Method> findMethods(Class<?> c, Class<?> i) { HashSet<Method> methods = new HashSet<Method>(); if (c.isInterface()) { if (!implementsInterface(c, i)) return methods; for (Method m : c.getDeclaredMethods()) methods.add(m); } for (Class<?> x : c.getInterfaces()) methods.addAll(findMethods(x, i)); return methods; } public static boolean sameSignature(Method a, Method b) { if (!a.getName().equals(b.getName())) return false; if (a.getParameterCount() != b.getParameterCount()) return false; for (int i = a.getParameterCount() - 1; i >= 0; i--) if (!a.getParameters()[i].getType().equals( b.getParameters()[i].getType())) return false; return true; } static boolean same(CtClass a, Class<?> b) throws NotFoundException { if (a.isArray()) { if (!b.isArray()) return false; return same(a.getComponentType(), b.getComponentType()); } return a.getName().equals(b.getName()); } public static boolean sameSignature(CtMethod a, Method b) throws NotFoundException { if (!a.getName().equals(b.getName())) return false; if (a.getParameterTypes().length != b.getParameterCount()) return false; for (int i = a.getParameterTypes().length - 1; i >= 0; i--) { CtClass typea = a.getParameterTypes()[i]; Class<?> typeb = b.getParameters()[i].getType(); if (!same(typea, typeb)) return false; } return true; } public static boolean declares(Class<?> clz, Method method) { for (Method m : clz.getDeclaredMethods()) if (sameSignature(m, method)) return true; return false; } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { Class<?> clz = innerLoader.loadClass(name); if (classMap.containsKey(clz)) return classMap.get(clz); Class<?> ret = clz; if (implementsInterface(clz, DaoService.class)) { Field daoField = findDaoField(clz); if (daoField != null) { // Modify DaoService implementation HashSet<Method> methods = findMethods(clz, DaoService.class); HashSet<Method> find = findMethods(daoField.getType(), DaoService.class); methods.retainAll(find); Method[] toadd = (Method[]) methods.stream() .filter(p -> !declares(clz, p)) .filter(p -> !Modifier.isStatic(p.getModifiers())) .toArray(size -> new Method[size]); if (toadd != null && toadd.length > 0) { try { final ClassLoader classLoader = this; ClassPool classPool = new ClassPool() { // use ServiceClassLoader as class loader in order // to use existing class name for modified class public ClassLoader getClassLoader() { return classLoader; } }; classPool.appendClassPath(new LoaderClassPath(this)); CtClass ctclass = classPool.get(clz.getName()); // add interface method implementation for (Method method : toadd) { CtClass ds = classPool.get(method .getDeclaringClass().getName()); boolean found = false; for (CtMethod m : ds.getDeclaredMethods()) if (sameSignature(m, method)) { CtMethod newmethod = null; try { newmethod = CtNewMethod.copy(m, ctclass, null); } catch (Exception e) { e.printStackTrace(); } // { Change method implementation to call // dao method StringBuffer code = new StringBuffer(); if (!newmethod.getReturnType().getName() .equals("void")) code.append("return "); code.append(daoField.getName() + "." + method.getName() + "($$);"); newmethod.setBody(code.toString()); // } ctclass.addMethod(newmethod); found = true; break; } if (!found) { logger.error("Unable to find CtMethod for method " + method + ", this DaoService can't be implemented automatically on class " + clz.getName()); } } ret = ctclass.toClass(); } catch (Exception e) { e.printStackTrace(); } } } } classMap.put(clz, ret); return ret; } }