View Javadoc

1   package ar.com.jiji.kaya.enhancer;
2   
3   import java.util.Set;
4   
5   import javassist.CannotCompileException;
6   import javassist.ClassClassPath;
7   import javassist.ClassPool;
8   import javassist.CtClass;
9   import javassist.CtMethod;
10  import javassist.Modifier;
11  import javassist.NotFoundException;
12  
13  import org.apache.commons.logging.Log;
14  import org.apache.commons.logging.LogFactory;
15  
16  import ar.com.jiji.kaya.reflect.ReflectionService;
17  import ar.com.jiji.kaya.utils.ValidateUtils;
18  
19  /**
20   * 
21   */
22  
23  /**
24   * Extiende cierta clase implementando los metodos abstractos que involucren
25   * operaciones con los dao. Para poder usar esta clase debemos seguir un par de
26   * convenciones. Si la implementacion del facade es abstracta, se busca si
27   * alguno de estos metodos es una operacion de creacion/actualizacion o borrado
28   * de alguna entidad. Los metodos de borrado deben ser de la forma
29   * <code>removeX(long id)</code> y los de creacion/actualizacion
30   * <code>saveX(&lt;X&gt; obj)</code>. Siendo <code>X</code> alguna entidad
31   * del sistema. Al encontrar estos metodos se crea una subclase del facade que
32   * implemente estos metodos, los cuales delegan su funcionalidad en
33   * <code>DaoUtils</code>. <br/> Se usa en general durante la fase de
34   * configuracion de la aplicacion por medio de Spring.
35   * 
36   * @author lparra
37   * @version $Revision$ $Date$
38   * @see ar.com.jiji.kaya.dao.DaoUtils
39   */
40  public class FacadeDaoEnhancer {
41  	private static final Log log = LogFactory.getLog(FacadeDaoEnhancer.class);
42  
43  	private ClassPool pool;
44  
45  	private long id;
46  
47  	private static FacadeDaoEnhancer enhancer;
48  
49  	private FacadeDaoEnhancer() {
50  		pool = ClassPool.getDefault();
51  		pool.insertClassPath(new ClassClassPath(this.getClass()));
52  	}
53  
54  	public static synchronized FacadeDaoEnhancer getInstance() {
55  		if (enhancer == null)
56  			enhancer = new FacadeDaoEnhancer();
57  		return enhancer;
58  	}
59  
60  	/**
61  	 * Se usa para obtener un numero unico para agregarle al nombre de la clase
62  	 * generada.
63  	 * 
64  	 * @return
65  	 */
66  	private synchronized String nextSequence() {
67  		// TODO cuando haya overflow, (para lo cual tiene que haber muuchas
68  		// clases creadas) va a tirar excepcion de frozen class
69  		id++;
70  		return System.currentTimeMillis() + "_" + id;
71  	}
72  
73  	/**
74  	 * Crea una subclase de la clase abstracta especificada e implementa los
75  	 * metodos abstractos para las operaciones de los dao.
76  	 * 
77  	 * @param clazz
78  	 *            La clase a inspeccionar. Puede ser una interfaz.
79  	 * @param modelPkg
80  	 *            El nombre del paquete donde estan las clases del modelo.
81  	 *            FIXME: Esto supone todos los objetos del dominio en el mismo
82  	 *            paquete y no es asi. Ver como solucionarlo.
83  	 * @return La subclase. Devuelve la misma clase si no es abstracta.
84  	 * @throws EnhancerException
85  	 */
86  	public Class enhance(Class clazz, String modelPkg) throws EnhancerException {
87  		ValidateUtils.argNotNull(clazz, "clase");
88  
89  		CtClass cImpl;
90  		try {
91  			cImpl = getCtClass(clazz);
92  
93  			int m = cImpl.getModifiers();
94  			if (Modifier.isAbstract(m)) {
95  				CtClass cEnhanced = createConcreteClass(clazz, modelPkg, cImpl);
96  				return cEnhanced.toClass();
97  			}
98  			log.info(clazz + " no es abstracta");
99  			return clazz;
100 		} catch (NotFoundException e) {
101 			throw new EnhancerException(e);
102 		} catch (CannotCompileException e) {
103 			throw new EnhancerException(e);
104 		} catch (ClassNotFoundException e) {
105 			throw new EnhancerException(e);
106 		}
107 	}
108 
109 	private CtClass createConcreteClass(Class clazz, String modelPkg,
110 			CtClass cImpl) throws NotFoundException, ClassNotFoundException,
111 			ClassNotFoundException, CannotCompileException {
112 		int m = cImpl.getModifiers();
113 		CtClass superClass;
114 		if (Modifier.isInterface(m))
115 			superClass = getCtClass("java.lang.Object");
116 		else
117 			superClass = cImpl;
118 		// TODO tomar en cuenta si tiene paquetes
119 		CtClass cEnhanced = pool.makeClass("Enhanced" + clazz.getSimpleName()
120 				+ "_" + nextSequence(), superClass);
121 		if (Modifier.isInterface(m))
122 			cEnhanced.addInterface(cImpl);
123 		addMissingMethods(cEnhanced, modelPkg);
124 		return cEnhanced;
125 	}
126 
127 	private CtClass getCtClass(String className) throws NotFoundException {
128 		return pool.get(className);
129 	}
130 
131 	private void addMissingMethods(CtClass clazz, String modelPkg)
132 			throws NotFoundException, CannotCompileException,
133 			ClassNotFoundException {
134 		CtMethod[] implMethods = clazz.getMethods();
135 		for (CtMethod method : implMethods) {
136 			if (Modifier.isAbstract(method.getModifiers())) {
137 				log.debug("adding " + method.getName());
138 				addMissingMethod(clazz, method, modelPkg);
139 			}
140 		}
141 	}
142 
143 	private void addMissingMethod(CtClass clazz, CtMethod method,
144 			String modelPkg) throws NotFoundException, CannotCompileException,
145 			ClassNotFoundException {
146 		if (isRemoveMethod(method))
147 			addRemoveMethod(clazz, method, modelPkg);
148 		if (isSaveMethod(method))
149 			addSaveMethod(clazz, method, modelPkg);
150 	}
151 
152 	private void addSaveMethod(CtClass clazz, CtMethod method, String modelPkg)
153 			throws NotFoundException, CannotCompileException,
154 			ClassNotFoundException {
155 		String className = modelPkg + "." + getModelName(method);
156 		CtMethod newMethod = new CtMethod(getCtClass(void.class), method
157 				.getName(),
158 				new CtClass[] { getCtClass(Class.forName(className)) }, clazz);
159 		newMethod.setBody("{ "
160 				+ "ar.com.jiji.kaya.dao.DaoUtils.save(getService(), "
161 				+ "Class.forName(\"" + className + "\"), $1);}");
162 
163 		newMethod.setModifiers(Modifier.PUBLIC);
164 		clazz.addMethod(newMethod);
165 	}
166 
167 	private void addRemoveMethod(CtClass clazz, CtMethod method, String modelPkg)
168 			throws CannotCompileException, NotFoundException {
169 		CtMethod newMethod = new CtMethod(getCtClass(void.class), method
170 				.getName(), new CtClass[] { getCtClass(Set.class) }, clazz);
171 		String className = modelPkg + "." + getModelName(method);
172 		newMethod.setBody("{ "
173 				+ "ar.com.jiji.kaya.dao.DaoUtils.remove(getService(), "
174 				+ "Class.forName(\"" + className + "\"), $1);}");
175 
176 		newMethod.setModifiers(Modifier.PUBLIC);
177 		clazz.addMethod(newMethod);
178 	}
179 
180 	private CtClass getCtClass(Class clazz) throws NotFoundException {
181 		return getCtClass(clazz.getName());
182 	}
183 
184 	private boolean isRemoveMethod(CtMethod method) throws NotFoundException {
185 		return (matchRemoveName(method) && matchRemoveReturnType(method) && matchRemoveArgs(method));
186 	}
187 
188 	private boolean matchRemoveArgs(CtMethod method) throws NotFoundException {
189 		CtClass[] args = method.getParameterTypes();
190 		return args.length == 1
191 				&& args[0].getName().equals(Set.class.getName());
192 	}
193 
194 	private boolean matchRemoveReturnType(CtMethod method)
195 			throws NotFoundException {
196 		return method.getReturnType().getName().equals("void");
197 	}
198 
199 	private boolean matchRemoveName(CtMethod method) {
200 		String name = method.getName();
201 		return name.startsWith("remove") && name.length() > 6;
202 	}
203 
204 	private boolean matchSaveName(CtMethod method) {
205 		String name = method.getName();
206 		return name.startsWith("save") && name.length() > 4;
207 	}
208 
209 	private String getModelName(CtMethod method) {
210 		String result = null;
211 		if (matchRemoveName(method))
212 			result = method.getName().substring(6);
213 		else if (matchSaveName(method))
214 			result = method.getName().substring(4);
215 		return result;
216 	}
217 
218 	private boolean isSaveMethod(CtMethod method) throws NotFoundException {
219 		String name = method.getName();
220 		return (name.startsWith("save") && name.length() > 4
221 				&& method.getReturnType().getName().equals("void") && method
222 				.getParameterTypes().length == 1);
223 	}
224 
225 	/**
226 	 * Implementa los metodos abstractos de la clase e instancia la clase.
227 	 * 
228 	 * @param c
229 	 * @param model
230 	 * @return
231 	 * @see #enhance(Class, String)
232 	 */
233 	public Object enhanceAndInstance(Class c, String model) {
234 		Class enhanced = enhance(c, model);
235 		return ReflectionService.newInstance(enhanced);
236 	}
237 }