001 package biz.hammurapi.convert; 002 003 import java.lang.reflect.InvocationTargetException; 004 import java.lang.reflect.Method; 005 import java.lang.reflect.Proxy; 006 import java.util.ArrayList; 007 import java.util.Collection; 008 import java.util.HashMap; 009 import java.util.Iterator; 010 import java.util.Map; 011 012 import biz.hammurapi.config.Context; 013 import biz.hammurapi.config.DomConfigFactory; 014 import biz.hammurapi.wrap.WrapperHandler; 015 016 /** 017 * Decorates objects based on their type and decoration providers 018 * available. Decoration is done with dynamic proxies. 019 * @author Pavel 020 */ 021 public class DecoratingService { 022 023 /** 024 * Converter which delegates to convert() method. 025 */ 026 public static final Decorator DECORATOR = new Decorator() { 027 028 public Object decorate(Object source) { 029 if (source==null) { 030 return null; 031 } 032 return DecoratingService.decorate(source); 033 } 034 035 }; 036 037 038 /** 039 * Decorates object using static decoration providers from the object's class 040 * classloader. If there are no decorations available or object doesn't 041 * impement any interfaces, then the object is returned as-is. 042 * @param obj Object to be decorated 043 * @return Decorated object (dynamic proxy instance) or original object if 044 * no decorations are available or object doesn't implement any interfaces. 045 */ 046 public static Object decorate(Object obj) { 047 return decorate(obj, null, null); 048 } 049 050 /** 051 * Decorates object using static decoration providers from the object's class 052 * classloader. If there are no decorations available or object doesn't 053 * impement any interfaces, then the object is returned as-is. 054 * @param obj Object to be decorated 055 * @param context Context for dynamic decorators. 056 * @return Decorated object (dynamic proxy instance) or original object if 057 * no decorations are available or object doesn't implement any interfaces. 058 */ 059 public static Object decorate(Object obj, Context context) { 060 return decorate(obj, context, null); 061 } 062 063 /** 064 * Decorates object using providers from the given classloader. 065 * @param obj Object to decorate. 066 * @param classLoader Class loader. 067 * @return Decorated object (dynamic proxy instance) or original object if 068 * no decorations are available or object doesn't implement any interfaces. 069 */ 070 public static Object decorate(Object obj, ClassLoader classLoader) { 071 return decorate(obj, null, classLoader); 072 } 073 074 /** 075 * Decorates object using given context for dynamic decorators and given class 076 * loader to load decorating providers. 077 * @param obj Object to decorate. 078 * @param context Context for dynamic decorators. 079 * @param classLoader Class loader. 080 * @return Decorated object (dynamic proxy instance) or original object if 081 * no decorations are available or object doesn't implement any interfaces. 082 */ 083 public static Object decorate(final Object obj, Context context, ClassLoader classLoader) { 084 if (obj==null) { 085 return null; 086 } 087 088 final Map interfaceMap = new HashMap(); 089 Class[] interfaces = WrapperHandler.getClassInterfaces(obj.getClass()); 090 for (int i=0; i<interfaces.length; ++i) { 091 interfaceMap.put(interfaces[i], obj); 092 } 093 094 if (interfaceMap.isEmpty()) { 095 return obj; 096 } 097 098 if (classLoader==null) { 099 classLoader = obj.getClass().getClassLoader(); 100 } 101 102 if (classLoader==null) { 103 classLoader = DecoratingService.class.getClassLoader(); 104 } 105 106 ClassLoaderDecoratorsEntry clde; 107 synchronized (classLoaderEntries) { 108 clde = (ClassLoaderDecoratorsEntry) classLoaderEntries.get(classLoader); 109 if (clde == null) { 110 clde = new ClassLoaderDecoratorsEntry(); 111 Iterator pit = DomConfigFactory.loadProviders(Decorator.class, classLoader); 112 while (pit.hasNext()) { 113 clde.addDecorators(pit.next()); 114 } 115 classLoaderEntries.put(classLoader, clde); 116 } 117 } 118 119 int iSize = interfaceMap.size(); 120 clde.collectDecorators(obj, interfaceMap, context, DECORATOR); 121 if (iSize==interfaceMap.size()) { 122 return obj; 123 } 124 125 Class[] allInterfaces = new Class[interfaceMap.size()]; 126 FilterInvocationHandler fih = new FilterInvocationHandler() { 127 128 public Object getMaster() { 129 return obj; 130 } 131 132 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 133 Object target = interfaceMap.get(method.getDeclaringClass()); 134 if (target==null) { 135 target = obj; 136 } 137 return method.invoke(target, args); 138 } 139 140 }; 141 142 return Proxy.newProxyInstance(classLoader, allInterfaces, fih); 143 } 144 145 /** 146 * Creates decorating closure. 147 * @return Decorating closure. 148 */ 149 public static Decorator getDecorator() { 150 return getDecorator(null, null); 151 } 152 153 /** 154 * Creates decorating closure. 155 * @param classLoader Class loader. 156 * @return Decorating closure. 157 */ 158 public static Decorator getDecorator(ClassLoader classLoader) { 159 return getDecorator(null, classLoader); 160 } 161 162 /** 163 * Creates decorating closure. 164 * @param context Context for dynamic decorators. 165 * @return Decorating closure. 166 */ 167 public static Decorator getDecorator(Context context) { 168 return getDecorator(context, null); 169 } 170 171 /** 172 * Creates decorating closure. 173 * @param context Context for dynamic decorators. 174 * @param classLoader Class loader. 175 * @return Decorating closure. 176 */ 177 public static Decorator getDecorator(final Context context, final ClassLoader classLoader) { 178 return new Decorator() { 179 180 public Object decorate(Object source) { 181 return DecoratingService.decorate(source, context, classLoader); 182 } 183 184 }; 185 } 186 187 private static class ClassLoaderDecoratorsEntry { 188 private static final String DECORATE = "decorate"; 189 190 Collection atomicDecorators = new ArrayList(); 191 192 void addDecorators(final Object provider) { 193 if (provider instanceof AtomicDecorator) { 194 atomicDecorators.add(provider); 195 } else if (provider instanceof AtomicDecoratorsBundle) { 196 atomicDecorators.addAll(((AtomicDecoratorsBundle) provider).getDecorators()); 197 } else if (provider instanceof Collection) { 198 Iterator it = ((Collection) provider).iterator(); 199 while (it.hasNext()) { 200 addDecorators(it.next()); 201 } 202 } else { // Introspection 203 Class pClass = provider.getClass(); 204 Method[] methods = pClass.getMethods(); 205 for (int i=0; i<methods.length; ++i) { 206 final Method method = methods[i]; 207 if (DECORATE.equals(method.getName()) && !void.class.equals(method.getReturnType())) { 208 final Class[] pTypes = method.getParameterTypes(); 209 if (pTypes.length == 1) { 210 atomicDecorators.add(new AtomicDecorator() { 211 212 public Object decorate(Object source, Context context, Decorator master) { 213 try { 214 return method.invoke(provider, new Object[] {source}); 215 } catch (IllegalAccessException e) { 216 throw new DecorationException(e); 217 } catch (InvocationTargetException e) { 218 throw new DecorationException(e); 219 } 220 } 221 222 public Class getSourceType() { 223 return pTypes[0]; 224 } 225 226 }); 227 } else if (pTypes.length == 2 && pTypes[1].equals(Decorator.class)) { 228 atomicDecorators.add(new AtomicDecorator() { 229 230 public Object decorate(Object source, Context context, Decorator master) { 231 try { 232 return method.invoke(provider, new Object[] {source, master}); 233 } catch (IllegalAccessException e) { 234 throw new DecorationException(e); 235 } catch (InvocationTargetException e) { 236 throw new DecorationException(e); 237 } 238 } 239 240 public Class getSourceType() { 241 return pTypes[0]; 242 } 243 244 }); 245 } else if (pTypes.length == 2 && pTypes[1].equals(Context.class)) { 246 atomicDecorators.add(new AtomicDecorator() { 247 248 public Object decorate(Object source, Context context, Decorator master) { 249 try { 250 return method.invoke(provider, new Object[] {source, context}); 251 } catch (IllegalAccessException e) { 252 throw new DecorationException(e); 253 } catch (InvocationTargetException e) { 254 throw new DecorationException(e); 255 } 256 } 257 258 public Class getSourceType() { 259 return pTypes[0]; 260 } 261 262 }); 263 } else if (pTypes.length == 3 && pTypes[1].equals(Context.class) && pTypes[1].equals(Decorator.class)) { 264 atomicDecorators.add(new AtomicDecorator() { 265 266 public Object decorate(Object source, Context context, Decorator master) { 267 try { 268 return method.invoke(provider, new Object[] {source, context, master}); 269 } catch (IllegalAccessException e) { 270 throw new DecorationException(e); 271 } catch (InvocationTargetException e) { 272 throw new DecorationException(e); 273 } 274 } 275 276 public Class getSourceType() { 277 return pTypes[0]; 278 } 279 280 }); 281 } 282 283 } 284 } 285 } 286 } 287 288 void collectDecorators(Object obj, Map interfaceMap, Context context, Decorator master) { 289 Iterator it = atomicDecorators.iterator(); 290 while (it.hasNext()) { 291 AtomicDecorator ad = (AtomicDecorator) it.next(); 292 if (ad.getSourceType().isInstance(obj)) { 293 Object decoration = ad.decorate(obj, context, master); 294 if (decoration!=null) { 295 boolean cascade = false; 296 Class[] dia = WrapperHandler.getClassInterfaces(decoration.getClass()); 297 for (int i=0; i<dia.length; ++i) { 298 if (!interfaceMap.containsKey(dia[i])) { 299 cascade=true; 300 interfaceMap.put(dia[i], decoration); 301 } 302 } 303 if (cascade) { 304 collectDecorators(decoration, interfaceMap, context, master); 305 } 306 } 307 } 308 } 309 } 310 311 } 312 313 /** 314 * Map of resolved converters: ClassLoader -> ClassLoaderConvertersEntry. 315 */ 316 private static Map classLoaderEntries = new HashMap(); 317 318 319 }