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(<X> 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
68
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
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 }