View Javadoc

1   /*
2    * Copyright (c) 2007, James Leigh All rights reserved.
3    * 
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions are met:
6    * 
7    * - Redistributions of source code must retain the above copyright notice, this
8    *   list of conditions and the following disclaimer.
9    * - Redistributions in binary form must reproduce the above copyright notice,
10   *   this list of conditions and the following disclaimer in the documentation
11   *   and/or other materials provided with the distribution. 
12   * - Neither the name of the openrdf.org nor the names of its contributors may
13   *   be used to endorse or promote products derived from this software without
14   *   specific prior written permission.
15   * 
16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26   * POSSIBILITY OF SUCH DAMAGE.
27   * 
28   */
29  package org.openrdf.elmo.impl;
30  
31  import static java.lang.reflect.Modifier.isAbstract;
32  
33  import java.lang.reflect.Method;
34  import java.security.AccessControlException;
35  import java.util.ArrayList;
36  import java.util.Arrays;
37  import java.util.Collection;
38  import java.util.HashMap;
39  import java.util.HashSet;
40  import java.util.LinkedHashSet;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.Set;
44  import java.util.TreeSet;
45  import java.util.concurrent.ConcurrentHashMap;
46  import java.util.concurrent.ConcurrentMap;
47  
48  import org.openrdf.elmo.ElmoEntityResolver;
49  import org.openrdf.elmo.ImplementationResolver;
50  import org.openrdf.elmo.annotations.factory;
51  import org.openrdf.elmo.dynacode.ClassFactory;
52  import org.openrdf.elmo.dynacode.ClassTemplate;
53  import org.openrdf.elmo.dynacode.CodeBuilder;
54  import org.openrdf.elmo.exceptions.ElmoCompositionException;
55  import org.slf4j.Logger;
56  import org.slf4j.LoggerFactory;
57  
58  /**
59   * This class takes a collection of roles (interfaces or classes) and uses
60   * composition to combine this into a single class.
61   * 
62   * @author James Leigh
63   * 
64   */
65  public class ElmoEntityCompositor implements ElmoEntityResolver {
66  	private static final String _$INTERCEPTED = "_$intercepted";
67  	private static final String PKG_PREFIX = "elmobeans.proxies._$";
68  	private static final String CLASS_PREFIX = "_$EntityProxy";
69  	private Logger logger = LoggerFactory.getLogger(ElmoEntityCompositor.class);
70  	private Set<String> special = new HashSet<String>(Arrays.asList(
71  			"groovy.lang.GroovyObject"));
72  	private ImplementationResolver interfaceResolver;
73  	private ImplementationResolver abstractResolver;
74  	private ClassFactory cp;
75  	private ConcurrentMap<List<Class<?>>, Class<?>> classes;
76  
77  	public ElmoEntityCompositor() {
78  		classes = new ConcurrentHashMap<List<Class<?>>, Class<?>>();
79  	}
80  
81  	public void setInterfaceBehaviourResolver(ImplementationResolver loader) {
82  		this.interfaceResolver = loader;
83  	}
84  
85  	public void setAbstractBehaviourResolver(ImplementationResolver loader) {
86  		this.abstractResolver = loader;
87  	}
88  
89  	public void setClassDefiner(ClassFactory definer) {
90  		this.cp = definer;
91  	}
92  
93  	public Class<?> resolveRoles(Class<?>[] roles) {
94  		List<Class<?>> list = Arrays.asList(roles);
95  		Class<?> type = classes.get(list);
96  		if (type == null) {
97  			type = createClass(list);
98  			Class<?> o = classes.putIfAbsent(list, type);
99  			if (o != null) {
100 				type = o;
101 			}
102 		}
103 		return type;
104 	}
105 
106 	private Class<?> createClass(List<Class<?>> roles) {
107 		try {
108 			List<Class<?>> types = new ArrayList<Class<?>>(roles);
109 			types = removeSuperClasses(types);
110 			Set<Class<?>> interfaces = new HashSet<Class<?>>(types.size());
111 			Set<Class<?>> abstracts = new HashSet<Class<?>>(types.size());
112 			Set<Class<?>> behaviours = new LinkedHashSet<Class<?>>(types.size());
113 			Map<Class<?>, MethodFactory> map = new HashMap();
114 			for (Class<?> role : types) {
115 				if (role.isAnnotationPresent(factory.class)) {
116 					for (Method m : role.getMethods()) {
117 						if (m.isAnnotationPresent(factory.class)) {
118 							map.put(m.getReturnType(), new MethodFactory(m,
119 									role));
120 						}
121 					}
122 				}
123 			}
124 			for (Class<?> role : types) {
125 				if (role.isAnnotationPresent(factory.class))
126 					continue;
127 				if (role.isInterface()) {
128 					interfaces.add(role);
129 				} else if (isAbstract(role.getModifiers())) {
130 					abstracts.add(role);
131 				} else {
132 					behaviours.add(role);
133 				}
134 				if (map.containsKey(role)) {
135 					behaviours.add(role);
136 				}
137 			}
138 			behaviours.addAll(abstractResolver.findImplementations(abstracts));
139 			behaviours.addAll(interfaceResolver.findImplementations(abstracts));
140 			behaviours.addAll(interfaceResolver.findImplementations(interfaces));
141 			String className = getJavaClassName(roles);
142 			return getComposedBehaviours(className, interfaces, behaviours, map);
143 		} catch (Exception e) {
144 			List<String> roleNames = new ArrayList<String>();
145 			for (Class<?> f : roles) {
146 				roleNames.add(f.getSimpleName());
147 			}
148 			throw new ElmoCompositionException(e.getMessage()
149 					+ " for entity with roles: " + roleNames, e);
150 		}
151 	}
152 
153 	@SuppressWarnings("unchecked")
154 	private List<Class<?>> removeSuperClasses(List<Class<?>> classes) {
155 		for (int i = classes.size() - 1; i >= 0; i--) {
156 			Class<?> c = classes.get(i);
157 			for (int j = classes.size() - 1; j >= 0; j--) {
158 				Class<?> d = classes.get(j);
159 				if (i != j && c.isAssignableFrom(d)) {
160 					classes.remove(i);
161 					break;
162 				}
163 			}
164 		}
165 		return classes;
166 	}
167 
168 	private Class<?> getComposedBehaviours(String className,
169 			Set<Class<?>> interfaces, Set<Class<?>> javaClasses,
170 			Map<Class<?>, MethodFactory> factories) throws Exception {
171 		try {
172 			return Class.forName(className, true, cp);
173 		} catch (ClassNotFoundException e) {
174 			synchronized (cp) {
175 				try {
176 					return Class.forName(className, true, cp);
177 				} catch (ClassNotFoundException e1) {
178 					return composeBehaviours(className, interfaces,
179 							javaClasses, factories);
180 				}
181 			}
182 		}
183 	}
184 
185 	private Class<?> composeBehaviours(String className,
186 			Set<Class<?>> interfaces, Set<Class<?>> javaClasses,
187 			Map<Class<?>, MethodFactory> factories) throws Exception {
188 		List<Behaviour> behaviours = new ArrayList<Behaviour>();
189 		ClassTemplate cc = cp.createClassTemplate(className);
190 		for (Class<?> clazz : javaClasses) {
191 			addInterfaces(clazz, interfaces);
192 		}
193 		for (Class<?> face : interfaces) {
194 			cc.addInterface(face);
195 		}
196 		for (Class<?> clazz : javaClasses) {
197 			Behaviour behaviour = new Behaviour();
198 			behaviour.setJavaClass(clazz);
199 			behaviour.setDeclaring(cc);
200 			behaviour.setFactories(factories);
201 			behaviour.init();
202 			behaviours.add(behaviour);
203 		}
204 		for (Method method : getMethods(javaClasses)) {
205 			if (!method.getName().startsWith("_$")) {
206 				Class<?> face = findInterface(method, cc.getInterfaces());
207 				if (face == null)
208 					face = method.getDeclaringClass();
209 				List<Behaviour> incepts = getInterceptors(behaviours, method,
210 						face, cc);
211 				if (incepts.size() > 0) {
212 					String name = _$INTERCEPTED + method.getName();
213 					if (implementMethod(behaviours, method, name, cc)) {
214 						interceptMethod(incepts, method, name, face, cc);
215 					}
216 				} else {
217 					implementMethod(behaviours, method, method.getName(), cc);
218 				}
219 			}
220 		}
221 		return cp.createClass(cc);
222 	}
223 
224 	private Collection<Method> getMethods(Set<Class<?>> javaClasses) {
225 		Map map = new HashMap();
226 		for (Class<?> jc : javaClasses) {
227 			for (Method m : jc.getMethods()) {
228 				if (isSpecial(m))
229 					continue;
230 				List list = new ArrayList(m.getParameterTypes().length + 1);
231 				list.add(m.getName());
232 				list.addAll(Arrays.asList(m.getParameterTypes()));
233 				if (!map.containsKey(list)) {
234 					map.put(list, m);
235 				}
236 			}
237 		}
238 		return map.values();
239 	}
240 
241 	private Set<Class<?>> addInterfaces(Class<?> clazz, Set<Class<?>> interfaces) {
242 		if (interfaces.contains(clazz))
243 			return interfaces;
244 		if (clazz.isInterface()) {
245 			interfaces.add(clazz);
246 		}
247 		Class<?> superclass = clazz.getSuperclass();
248 		if (superclass != null) {
249 			addInterfaces(superclass, interfaces);
250 		}
251 		for (Class<?> face : clazz.getInterfaces()) {
252 			if (!isSpecial(face)) {
253 				addInterfaces(face, interfaces);
254 			}
255 		}
256 		return interfaces;
257 	}
258 
259 	private boolean isSpecial(Method m) {
260 		return Object.class.equals(m.getDeclaringClass());
261 	}
262 
263 	private boolean isSpecial(Class<?> face) {
264 		return special.contains(face.getName());
265 	}
266 
267 	private String getJavaClassName(Collection<Class<?>> javaClasses) {
268 		String phex = packagesToHexString(javaClasses);
269 		String chex = classesToHexString(javaClasses);
270 		return PKG_PREFIX + phex + "." + CLASS_PREFIX + chex;
271 	}
272 
273 	private String packagesToHexString(Collection<Class<?>> javaClasses) {
274 		TreeSet<String> names = new TreeSet<String>();
275 		for (Class<?> clazz : javaClasses) {
276 			names.add(clazz.getPackage().getName());
277 		}
278 		return toHexString(names);
279 	}
280 
281 	private String classesToHexString(Collection<Class<?>> javaClasses) {
282 		TreeSet<String> names = new TreeSet<String>();
283 		for (Class<?> clazz : javaClasses) {
284 			names.add(clazz.getName());
285 		}
286 		return toHexString(names);
287 	}
288 
289 	private String toHexString(TreeSet<String> names) {
290 		long hashCode = 0;
291 		for (String name : names) {
292 			hashCode = 31 * hashCode + name.hashCode();
293 		}
294 		return Long.toHexString(hashCode);
295 	}
296 
297 	private boolean implementMethod(List<Behaviour> behaviours, Method method,
298 			String name, ClassTemplate cc) throws Exception {
299 		Class<?> type = method.getReturnType();
300 		boolean voidReturnType = type.equals(Void.TYPE);
301 		boolean booleanReturnType = type.equals(Boolean.TYPE);
302 		boolean primitiveReturnType = type.isPrimitive();
303 		StringBuilder body = new StringBuilder();
304 		if (!voidReturnType && primitiveReturnType) {
305 			body.append(type.getName()).append(" result;");
306 		} else if (!voidReturnType) {
307 			body.append("Object result;");
308 		}
309 		int implemented = 0;
310 		StringBuilder eval = null;
311 		for (Behaviour behaviour : behaviours) {
312 			if (behaviour.isMethodPresent(method)) {
313 				implemented++;
314 				if (!voidReturnType)
315 					body.append("result = ");
316 				eval = new StringBuilder();
317 				eval.append(behaviour.getGetterName()).append("()");
318 				eval.append(".").append(method.getName()).append("($$);");
319 				body.append(eval);
320 				if (booleanReturnType) {
321 					body.append("if (result) return result;");
322 				} else if (!voidReturnType && primitiveReturnType) {
323 					body.append("if (result != 0) return ($r) result;");
324 				} else if (!voidReturnType) {
325 					body.append("if (result != null) return ($r) result;");
326 				}
327 			}
328 		}
329 		if (!voidReturnType)
330 			body.append("return ($r) result;");
331 		if (implemented == 1 && eval != null) {
332 			cc.createMethod(method.getReturnType(), name,
333 					method.getParameterTypes()).code(
334 					"return ($r) " + eval.toString()).end();
335 			return true;
336 		} else if (implemented > 1) {
337 			cc.createMethod(method.getReturnType(), name,
338 					method.getParameterTypes()).code(body.toString()).end();
339 			return true;
340 		}
341 		return false;
342 	}
343 
344 	private List<Behaviour> getInterceptors(List<Behaviour> behaviours,
345 			Method method, Class<?> face, ClassTemplate cc) throws Exception {
346 		List<Behaviour> result = new ArrayList<Behaviour>(behaviours.size());
347 		for (Behaviour behaviour : behaviours) {
348 			if (behaviour.invokeCondition(method, face)) {
349 				result.add(behaviour);
350 			}
351 		}
352 		return result;
353 	}
354 
355 	private void interceptMethod(List<Behaviour> interceptors, Method method,
356 			String name, Class<?> face, ClassTemplate cc) throws Exception {
357 		CodeBuilder body = cc.overrideMethod(method);
358 		body.code("return ($r) new ").code(
359 				InvocationContextImpl.class.getName());
360 		body.code("($0, ");
361 		Method declaredMethod = face.getDeclaredMethod(method.getName(), method
362 				.getParameterTypes());
363 		body.insert(declaredMethod);
364 		body.code(", $args, ");
365 		body.insertMethod(name, method.getParameterTypes()).code(")");
366 		for (Behaviour behaviour : interceptors) {
367 			for (Method m : behaviour.getAroundInvoke(method, face, cc)) {
368 				body.code(".appendInvocation(");
369 				body.code(behaviour.getGetterName()).code("(), ");
370 				body.insert(m);
371 				body.code(")");
372 			}
373 		}
374 		body.code(".proceed();");
375 		body.end();
376 	}
377 
378 	private Class<?> findInterface(Method method, Class<?>[] interfaces)
379 			throws Exception {
380 		Class<?> declaring = null;
381 		for (Class<?> face : interfaces) {
382 			try {
383 				face.getDeclaredMethod(method.getName(), method
384 						.getParameterTypes());
385 				declaring = face;
386 			} catch (AccessControlException e) {
387 				logger.warn("Could not access " + face.getName(), e);
388 				declaring = findInterface(method, face.getInterfaces());
389 			} catch (NoSuchMethodException e) {
390 				declaring = findInterface(method, face.getInterfaces());
391 			}
392 			if (declaring != null)
393 				return declaring;
394 		}
395 		return declaring;
396 	}
397 }