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 */
011package com.thoughtworks.proxy.toys.hotswap;
012
013import java.io.IOException;
014import java.io.ObjectInputStream;
015import java.io.ObjectOutputStream;
016import java.lang.reflect.Method;
017
018import com.thoughtworks.proxy.ProxyFactory;
019import com.thoughtworks.proxy.kit.ObjectReference;
020import com.thoughtworks.proxy.toys.delegate.DelegatingInvoker;
021import 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 */
033public 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}