001    /*****************************************************************************
002     * Copyright (C) PicoContainer Organization. All rights reserved.            *
003     * ------------------------------------------------------------------------- *
004     * The software in this package is published under the terms of the BSD      *
005     * style license a copy of which has been included with this distribution in *
006     * the LICENSE.txt file.                                                     *
007     *                                                                           *
008     * Original code by                                                          *
009     *****************************************************************************/
010    package org.picocontainer.defaults;
011    
012    import java.lang.reflect.InvocationHandler;
013    import java.lang.reflect.InvocationTargetException;
014    import java.lang.reflect.Method;
015    import java.lang.reflect.Proxy;
016    
017    import org.picocontainer.ComponentAdapter;
018    import org.picocontainer.ComponentMonitor;
019    import org.picocontainer.PicoContainer;
020    import org.picocontainer.PicoInitializationException;
021    import org.picocontainer.PicoIntrospectionException;
022    
023    /**
024     * This component adapter makes it possible to hide the implementation
025     * of a real subject (behind a proxy) provided the key is an interface.
026     * <p/>
027     * This class exists here, because a) it has no deps on external jars, b) dynamic proxy is quite easy.
028     * The user is prompted to look at picocontainer-gems for alternate and bigger implementations.
029     *
030     * @author Aslak Hellesøy
031     * @author Paul Hammant
032     * @see org.picocontainer.gems.HotSwappingComponentAdapter for a more feature-rich version of this class.
033     * @since 1.2, moved from package {@link org.picocontainer.alternatives}
034     */
035    public class ImplementationHidingComponentAdapter extends DecoratingComponentAdapter {
036        private final boolean strict;
037    
038        /**
039         * Creates an ImplementationHidingComponentAdapter with a delegate 
040         * @param delegate the component adapter to which this adapter delegates
041         * @param strict the scrict mode boolean
042         */
043        public ImplementationHidingComponentAdapter(ComponentAdapter delegate, boolean strict) {
044            super(delegate);
045            this.strict = strict;
046        }
047    
048        public Object getComponentInstance(final PicoContainer container)
049                throws PicoInitializationException, PicoIntrospectionException, AssignabilityRegistrationException, NotConcreteRegistrationException {
050    
051            Object componentKey = getDelegate().getComponentKey();
052            Class[] classes = null;
053            if (componentKey instanceof Class && ((Class) getDelegate().getComponentKey()).isInterface()) {
054                classes = new Class[]{(Class) getDelegate().getComponentKey()};
055            } else if (componentKey instanceof Class[]) {
056                classes = (Class[]) componentKey;
057            } else {
058                if(strict) {
059                    throw new PicoIntrospectionException("In strict mode, " + getClass().getName() + " only allows components registered with interface keys (java.lang.Class or java.lang.Class[])");
060                }
061                return getDelegate().getComponentInstance(container);
062            }
063    
064            Class[] interfaces = verifyInterfacesOnly(classes);
065            return createProxy(interfaces, container, getDelegate().getComponentImplementation().getClassLoader());
066        }
067    
068        private Object createProxy(Class[] interfaces, final PicoContainer container, final ClassLoader classLoader) {
069            return Proxy.newProxyInstance(classLoader,
070                    interfaces, new InvocationHandler() {
071                        public Object invoke(final Object proxy, final Method method,
072                                             final Object[] args)
073                                throws Throwable {
074                            Object componentInstance = getDelegate().getComponentInstance(container);
075                            ComponentMonitor componentMonitor = currentMonitor();
076                            try {
077                                componentMonitor.invoking(method, componentInstance);
078                                long startTime = System.currentTimeMillis();
079                                Object object = method.invoke(componentInstance, args);
080                                componentMonitor.invoked(method, componentInstance, System.currentTimeMillis() - startTime);
081                                return object;
082                            } catch (final InvocationTargetException ite) {
083                                componentMonitor.invocationFailed(method, componentInstance, ite);
084                                throw ite.getTargetException();
085                            }
086                        }
087                    });
088        }
089    
090        private Class[] verifyInterfacesOnly(Class[] classes) {
091            for (int i = 0; i < classes.length; i++) {
092                if(!classes[i].isInterface()) {
093                    throw new PicoIntrospectionException("Class keys must be interfaces. " + classes[i] + " is not an interface.");
094                }
095            }
096            return classes;
097        }
098    
099    }