001/*
002 * Copyright 2002-2012 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.aop.support;
018
019import java.util.Map;
020import java.util.WeakHashMap;
021
022import org.aopalliance.intercept.MethodInvocation;
023
024import org.springframework.aop.DynamicIntroductionAdvice;
025import org.springframework.aop.IntroductionInterceptor;
026import org.springframework.aop.ProxyMethodInvocation;
027
028/**
029 * Convenient implementation of the
030 * {@link org.springframework.aop.IntroductionInterceptor} interface.
031 *
032 * <p>This differs from {@link DelegatingIntroductionInterceptor} in that a single
033 * instance of this class can be used to advise multiple target objects, and each target
034 * object will have its <i>own</i> delegate (whereas DelegatingIntroductionInterceptor
035 * shares the same delegate, and hence the same state across all targets).
036 *
037 * <p>The {@code suppressInterface} method can be used to suppress interfaces
038 * implemented by the delegate class but which should not be introduced to the
039 * owning AOP proxy.
040 *
041 * <p>An instance of this class is serializable if the delegates are.
042 *
043 * <p><i>Note: There are some implementation similarities between this class and
044 * {@link DelegatingIntroductionInterceptor} that suggest a possible refactoring
045 * to extract a common ancestor class in the future.</i>
046 *
047 * @author Adrian Colyer
048 * @author Juergen Hoeller
049 * @since 2.0
050 * @see #suppressInterface
051 * @see DelegatingIntroductionInterceptor
052 */
053@SuppressWarnings("serial")
054public class DelegatePerTargetObjectIntroductionInterceptor extends IntroductionInfoSupport
055                implements IntroductionInterceptor {
056
057        /**
058         * Hold weak references to keys as we don't want to interfere with garbage collection..
059         */
060        private final Map<Object, Object> delegateMap = new WeakHashMap<Object, Object>();
061
062        private Class<?> defaultImplType;
063
064        private Class<?> interfaceType;
065
066
067        public DelegatePerTargetObjectIntroductionInterceptor(Class<?> defaultImplType, Class<?> interfaceType) {
068                this.defaultImplType = defaultImplType;
069                this.interfaceType = interfaceType;
070                // Create a new delegate now (but don't store it in the map).
071                // We do this for two reasons:
072                // 1) to fail early if there is a problem instantiating delegates
073                // 2) to populate the interface map once and once only
074                Object delegate = createNewDelegate();
075                implementInterfacesOnObject(delegate);
076                suppressInterface(IntroductionInterceptor.class);
077                suppressInterface(DynamicIntroductionAdvice.class);
078        }
079
080
081        /**
082         * Subclasses may need to override this if they want to perform custom
083         * behaviour in around advice. However, subclasses should invoke this
084         * method, which handles introduced interfaces and forwarding to the target.
085         */
086        @Override
087        public Object invoke(MethodInvocation mi) throws Throwable {
088                if (isMethodOnIntroducedInterface(mi)) {
089                        Object delegate = getIntroductionDelegateFor(mi.getThis());
090
091                        // Using the following method rather than direct reflection,
092                        // we get correct handling of InvocationTargetException
093                        // if the introduced method throws an exception.
094                        Object retVal = AopUtils.invokeJoinpointUsingReflection(delegate, mi.getMethod(), mi.getArguments());
095
096                        // Massage return value if possible: if the delegate returned itself,
097                        // we really want to return the proxy.
098                        if (retVal == delegate && mi instanceof ProxyMethodInvocation) {
099                                retVal = ((ProxyMethodInvocation) mi).getProxy();
100                        }
101                        return retVal;
102                }
103
104                return doProceed(mi);
105        }
106
107        /**
108         * Proceed with the supplied {@link org.aopalliance.intercept.MethodInterceptor}.
109         * Subclasses can override this method to intercept method invocations on the
110         * target object which is useful when an introduction needs to monitor the object
111         * that it is introduced into. This method is <strong>never</strong> called for
112         * {@link MethodInvocation MethodInvocations} on the introduced interfaces.
113         */
114        protected Object doProceed(MethodInvocation mi) throws Throwable {
115                // If we get here, just pass the invocation on.
116                return mi.proceed();
117        }
118
119        private Object getIntroductionDelegateFor(Object targetObject) {
120                synchronized (this.delegateMap) {
121                        if (this.delegateMap.containsKey(targetObject)) {
122                                return this.delegateMap.get(targetObject);
123                        }
124                        else {
125                                Object delegate = createNewDelegate();
126                                this.delegateMap.put(targetObject, delegate);
127                                return delegate;
128                        }
129                }
130        }
131
132        private Object createNewDelegate() {
133                try {
134                        return this.defaultImplType.newInstance();
135                }
136                catch (Throwable ex) {
137                        throw new IllegalArgumentException("Cannot create default implementation for '" +
138                                        this.interfaceType.getName() + "' mixin (" + this.defaultImplType.getName() + "): " + ex);
139                }
140        }
141
142}