001    /*
002     * (c) 2003-2005, 2009, 2010 ThoughtWorks Ltd
003     * All rights reserved.
004     *
005     * The software in this package is published under the terms of the BSD
006     * style license a copy of which has been included with this distribution in
007     * the LICENSE.txt file.
008     * 
009     * Created on 11-May-2004
010     */
011    package com.thoughtworks.proxy.toys.hotswap;
012    
013    import java.io.IOException;
014    import java.io.ObjectInputStream;
015    import java.io.ObjectOutputStream;
016    import java.lang.reflect.Method;
017    
018    import com.thoughtworks.proxy.ProxyFactory;
019    import com.thoughtworks.proxy.kit.ObjectReference;
020    import com.thoughtworks.proxy.toys.delegate.DelegatingInvoker;
021    import com.thoughtworks.proxy.toys.delegate.DelegationMode;
022    
023    
024    /**
025     * A {@link DelegatingInvoker} implementation that allows the exchange of the delegate.
026     *
027     * @author Aslak Hellesøy
028     * @author Dan North
029     * @author Paul Hammant
030     * @author Jörg Schaible
031     * @since 0.1
032     */
033    public class HotSwappingInvoker<T> extends DelegatingInvoker<Object> {
034        private static final long serialVersionUID = 1L;
035        private static final Method hotswap;
036        private static final Method checkForCycle;
037    
038        static {
039            try {
040                hotswap = Swappable.class.getMethod("hotswap", new Class[]{Object.class});
041                checkForCycle = CycleCheck.class.getMethod("checkForCycle");
042            } catch (NoSuchMethodException e) {
043                throw new ExceptionInInitializerError(e.toString());
044            }
045        }
046    
047        /**
048         * Internal interface used to detect cyclic swapping activity.
049         *
050         * @since 0.2
051         */
052        protected static interface CycleCheck {
053            /**
054             * Checks for a cyclic swap action.
055             *
056             * @throws IllegalStateException if cycle detected
057             * @since 0.2
058             */
059            void checkForCycle();
060        }
061    
062        private Class<?>[] types;
063        private transient boolean executed = false;
064        private transient ThreadLocal<Object> delegate;
065    
066        /**
067         * Construct a HotSwappingInvoker.
068         *
069         * @param types             the types of the proxy
070         * @param proxyFactory      the {@link ProxyFactory} to use
071         * @param delegateReference the {@link ObjectReference} with the delegate
072         * @param delegationMode    {@link DelegationMode#DIRECT} or {@link DelegationMode#SIGNATURE}
073         * @since 1.0
074         */
075        public HotSwappingInvoker(
076                final Class<?>[] types, final ProxyFactory proxyFactory, final ObjectReference<Object> delegateReference,
077                final DelegationMode delegationMode) {
078            super(proxyFactory, delegateReference, delegationMode);
079            this.types = types;
080            this.delegate = new ThreadLocal<Object>();
081        }
082    
083        @Override
084        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
085            Object result;
086            try {
087                delegate.set(delegate()); // ensure delegate will not change during invocation
088                if (method.equals(hotswap)) {
089                    result = hotswap(args[0]);
090                } else if (method.equals(checkForCycle)) {
091                    if (executed) {
092                        throw new IllegalStateException("Cyclic dependency");
093                    } else {
094                        if (delegate() instanceof CycleCheck) {
095                            executed = true;
096                            CycleCheck.class.cast(delegate()).checkForCycle();
097                            executed = false;
098                        }
099                    }
100                    return Void.TYPE;
101                } else {
102                    result = super.invoke(proxy, method, args);
103                }
104            } finally {
105                delegate.set(null);
106            }
107            return result;
108        }
109    
110        @Override
111        protected Object delegate() {
112            final Object currentDelegate;
113            currentDelegate = delegate.get();
114            if (currentDelegate == null) {
115                return super.delegate();
116            } else {
117                return currentDelegate;
118            }
119        }
120    
121        /**
122         * Exchange the current delegate.
123         *
124         * @param newDelegate the new delegate
125         * @return the old delegate
126         * @throws IllegalStateException if cyclic swapping action is detected
127         * @since 0.1
128         */
129        protected Object hotswap(final Object newDelegate) {
130            ObjectReference<Object> ref = getDelegateReference();
131            Object result = ref.get();
132            // Note, for the cycle detection the delegate has to be set first
133            delegate.set(newDelegate);
134            ref.set(newDelegate);
135            if (newDelegate instanceof CycleCheck) {
136                CycleCheck.class.cast(newDelegate).checkForCycle();
137            }
138            return result;
139        }
140    
141        /**
142         * Create a proxy for this Invoker. The proxy implements all the types given as parameter to the constructor and
143         * implements additionally the {@link Swappable} interface.
144         *
145         * @return the new proxy
146         * @since 0.1
147         */
148        public T proxy() {
149            Class<?>[] typesWithSwappable = new Class[types.length + 2];
150            System.arraycopy(types, 0, typesWithSwappable, 0, types.length);
151            typesWithSwappable[types.length] = Swappable.class;
152            typesWithSwappable[types.length + 1] = CycleCheck.class;
153            return getProxyFactory().<T>createProxy(this, typesWithSwappable);
154        }
155    
156        private void writeObject(final ObjectOutputStream out) throws IOException {
157            out.defaultWriteObject();
158        }
159    
160        private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
161            in.defaultReadObject();
162            this.delegate = new ThreadLocal<Object>();
163        }
164    }