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 -&gt; ClassLoaderConvertersEntry. 
315             */
316            private static Map classLoaderEntries = new HashMap(); 
317    
318    
319    }