001/*
002 * Copyright 2002-2018 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.framework;
018
019import java.io.IOException;
020import java.io.ObjectInputStream;
021import java.lang.reflect.Method;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.Collection;
025import java.util.List;
026import java.util.Map;
027import java.util.concurrent.ConcurrentHashMap;
028
029import org.aopalliance.aop.Advice;
030
031import org.springframework.aop.Advisor;
032import org.springframework.aop.DynamicIntroductionAdvice;
033import org.springframework.aop.IntroductionAdvisor;
034import org.springframework.aop.IntroductionInfo;
035import org.springframework.aop.TargetSource;
036import org.springframework.aop.support.DefaultIntroductionAdvisor;
037import org.springframework.aop.support.DefaultPointcutAdvisor;
038import org.springframework.aop.target.EmptyTargetSource;
039import org.springframework.aop.target.SingletonTargetSource;
040import org.springframework.util.Assert;
041import org.springframework.util.ClassUtils;
042import org.springframework.util.CollectionUtils;
043
044/**
045 * Base class for AOP proxy configuration managers.
046 * These are not themselves AOP proxies, but subclasses of this class are
047 * normally factories from which AOP proxy instances are obtained directly.
048 *
049 * <p>This class frees subclasses of the housekeeping of Advices
050 * and Advisors, but doesn't actually implement proxy creation
051 * methods, which are provided by subclasses.
052 *
053 * <p>This class is serializable; subclasses need not be.
054 * This class is used to hold snapshots of proxies.
055 *
056 * @author Rod Johnson
057 * @author Juergen Hoeller
058 * @see org.springframework.aop.framework.AopProxy
059 */
060public class AdvisedSupport extends ProxyConfig implements Advised {
061
062        /** use serialVersionUID from Spring 2.0 for interoperability */
063        private static final long serialVersionUID = 2651364800145442165L;
064
065
066        /**
067         * Canonical TargetSource when there's no target, and behavior is
068         * supplied by the advisors.
069         */
070        public static final TargetSource EMPTY_TARGET_SOURCE = EmptyTargetSource.INSTANCE;
071
072
073        /** Package-protected to allow direct access for efficiency */
074        TargetSource targetSource = EMPTY_TARGET_SOURCE;
075
076        /** Whether the Advisors are already filtered for the specific target class */
077        private boolean preFiltered = false;
078
079        /** The AdvisorChainFactory to use */
080        AdvisorChainFactory advisorChainFactory = new DefaultAdvisorChainFactory();
081
082        /** Cache with Method as key and advisor chain List as value */
083        private transient Map<MethodCacheKey, List<Object>> methodCache;
084
085        /**
086         * Interfaces to be implemented by the proxy. Held in List to keep the order
087         * of registration, to create JDK proxy with specified order of interfaces.
088         */
089        private List<Class<?>> interfaces = new ArrayList<Class<?>>();
090
091        /**
092         * List of Advisors. If an Advice is added, it will be wrapped
093         * in an Advisor before being added to this List.
094         */
095        private List<Advisor> advisors = new ArrayList<Advisor>();
096
097        /**
098         * Array updated on changes to the advisors list, which is easier
099         * to manipulate internally.
100         */
101        private Advisor[] advisorArray = new Advisor[0];
102
103
104        /**
105         * No-arg constructor for use as a JavaBean.
106         */
107        public AdvisedSupport() {
108                initMethodCache();
109        }
110
111        /**
112         * Create a AdvisedSupport instance with the given parameters.
113         * @param interfaces the proxied interfaces
114         */
115        public AdvisedSupport(Class<?>... interfaces) {
116                this();
117                setInterfaces(interfaces);
118        }
119
120        /**
121         * Initialize the method cache.
122         */
123        private void initMethodCache() {
124                this.methodCache = new ConcurrentHashMap<MethodCacheKey, List<Object>>(32);
125        }
126
127
128        /**
129         * Set the given object as target.
130         * Will create a SingletonTargetSource for the object.
131         * @see #setTargetSource
132         * @see org.springframework.aop.target.SingletonTargetSource
133         */
134        public void setTarget(Object target) {
135                setTargetSource(new SingletonTargetSource(target));
136        }
137
138        @Override
139        public void setTargetSource(TargetSource targetSource) {
140                this.targetSource = (targetSource != null ? targetSource : EMPTY_TARGET_SOURCE);
141        }
142
143        @Override
144        public TargetSource getTargetSource() {
145                return this.targetSource;
146        }
147
148        /**
149         * Set a target class to be proxied, indicating that the proxy
150         * should be castable to the given class.
151         * <p>Internally, an {@link org.springframework.aop.target.EmptyTargetSource}
152         * for the given target class will be used. The kind of proxy needed
153         * will be determined on actual creation of the proxy.
154         * <p>This is a replacement for setting a "targetSource" or "target",
155         * for the case where we want a proxy based on a target class
156         * (which can be an interface or a concrete class) without having
157         * a fully capable TargetSource available.
158         * @see #setTargetSource
159         * @see #setTarget
160         */
161        public void setTargetClass(Class<?> targetClass) {
162                this.targetSource = EmptyTargetSource.forClass(targetClass);
163        }
164
165        @Override
166        public Class<?> getTargetClass() {
167                return this.targetSource.getTargetClass();
168        }
169
170        @Override
171        public void setPreFiltered(boolean preFiltered) {
172                this.preFiltered = preFiltered;
173        }
174
175        @Override
176        public boolean isPreFiltered() {
177                return this.preFiltered;
178        }
179
180        /**
181         * Set the advisor chain factory to use.
182         * <p>Default is a {@link DefaultAdvisorChainFactory}.
183         */
184        public void setAdvisorChainFactory(AdvisorChainFactory advisorChainFactory) {
185                Assert.notNull(advisorChainFactory, "AdvisorChainFactory must not be null");
186                this.advisorChainFactory = advisorChainFactory;
187        }
188
189        /**
190         * Return the advisor chain factory to use (never {@code null}).
191         */
192        public AdvisorChainFactory getAdvisorChainFactory() {
193                return this.advisorChainFactory;
194        }
195
196
197        /**
198         * Set the interfaces to be proxied.
199         */
200        public void setInterfaces(Class<?>... interfaces) {
201                Assert.notNull(interfaces, "Interfaces must not be null");
202                this.interfaces.clear();
203                for (Class<?> ifc : interfaces) {
204                        addInterface(ifc);
205                }
206        }
207
208        /**
209         * Add a new proxied interface.
210         * @param intf the additional interface to proxy
211         */
212        public void addInterface(Class<?> intf) {
213                Assert.notNull(intf, "Interface must not be null");
214                if (!intf.isInterface()) {
215                        throw new IllegalArgumentException("[" + intf.getName() + "] is not an interface");
216                }
217                if (!this.interfaces.contains(intf)) {
218                        this.interfaces.add(intf);
219                        adviceChanged();
220                }
221        }
222
223        /**
224         * Remove a proxied interface.
225         * <p>Does nothing if the given interface isn't proxied.
226         * @param intf the interface to remove from the proxy
227         * @return {@code true} if the interface was removed; {@code false}
228         * if the interface was not found and hence could not be removed
229         */
230        public boolean removeInterface(Class<?> intf) {
231                return this.interfaces.remove(intf);
232        }
233
234        @Override
235        public Class<?>[] getProxiedInterfaces() {
236                return ClassUtils.toClassArray(this.interfaces);
237        }
238
239        @Override
240        public boolean isInterfaceProxied(Class<?> intf) {
241                for (Class<?> proxyIntf : this.interfaces) {
242                        if (intf.isAssignableFrom(proxyIntf)) {
243                                return true;
244                        }
245                }
246                return false;
247        }
248
249
250        @Override
251        public final Advisor[] getAdvisors() {
252                return this.advisorArray;
253        }
254
255        @Override
256        public void addAdvisor(Advisor advisor) {
257                int pos = this.advisors.size();
258                addAdvisor(pos, advisor);
259        }
260
261        @Override
262        public void addAdvisor(int pos, Advisor advisor) throws AopConfigException {
263                if (advisor instanceof IntroductionAdvisor) {
264                        validateIntroductionAdvisor((IntroductionAdvisor) advisor);
265                }
266                addAdvisorInternal(pos, advisor);
267        }
268
269        @Override
270        public boolean removeAdvisor(Advisor advisor) {
271                int index = indexOf(advisor);
272                if (index == -1) {
273                        return false;
274                }
275                else {
276                        removeAdvisor(index);
277                        return true;
278                }
279        }
280
281        @Override
282        public void removeAdvisor(int index) throws AopConfigException {
283                if (isFrozen()) {
284                        throw new AopConfigException("Cannot remove Advisor: Configuration is frozen.");
285                }
286                if (index < 0 || index > this.advisors.size() - 1) {
287                        throw new AopConfigException("Advisor index " + index + " is out of bounds: " +
288                                        "This configuration only has " + this.advisors.size() + " advisors.");
289                }
290
291                Advisor advisor = this.advisors.get(index);
292                if (advisor instanceof IntroductionAdvisor) {
293                        IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
294                        // We need to remove introduction interfaces.
295                        for (int j = 0; j < ia.getInterfaces().length; j++) {
296                                removeInterface(ia.getInterfaces()[j]);
297                        }
298                }
299
300                this.advisors.remove(index);
301                updateAdvisorArray();
302                adviceChanged();
303        }
304
305        @Override
306        public int indexOf(Advisor advisor) {
307                Assert.notNull(advisor, "Advisor must not be null");
308                return this.advisors.indexOf(advisor);
309        }
310
311        @Override
312        public boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException {
313                Assert.notNull(a, "Advisor a must not be null");
314                Assert.notNull(b, "Advisor b must not be null");
315                int index = indexOf(a);
316                if (index == -1) {
317                        return false;
318                }
319                removeAdvisor(index);
320                addAdvisor(index, b);
321                return true;
322        }
323
324        /**
325         * Add all of the given advisors to this proxy configuration.
326         * @param advisors the advisors to register
327         */
328        public void addAdvisors(Advisor... advisors) {
329                addAdvisors(Arrays.asList(advisors));
330        }
331
332        /**
333         * Add all of the given advisors to this proxy configuration.
334         * @param advisors the advisors to register
335         */
336        public void addAdvisors(Collection<Advisor> advisors) {
337                if (isFrozen()) {
338                        throw new AopConfigException("Cannot add advisor: Configuration is frozen.");
339                }
340                if (!CollectionUtils.isEmpty(advisors)) {
341                        for (Advisor advisor : advisors) {
342                                if (advisor instanceof IntroductionAdvisor) {
343                                        validateIntroductionAdvisor((IntroductionAdvisor) advisor);
344                                }
345                                Assert.notNull(advisor, "Advisor must not be null");
346                                this.advisors.add(advisor);
347                        }
348                        updateAdvisorArray();
349                        adviceChanged();
350                }
351        }
352
353        private void validateIntroductionAdvisor(IntroductionAdvisor advisor) {
354                advisor.validateInterfaces();
355                // If the advisor passed validation, we can make the change.
356                Class<?>[] ifcs = advisor.getInterfaces();
357                for (Class<?> ifc : ifcs) {
358                        addInterface(ifc);
359                }
360        }
361
362        private void addAdvisorInternal(int pos, Advisor advisor) throws AopConfigException {
363                Assert.notNull(advisor, "Advisor must not be null");
364                if (isFrozen()) {
365                        throw new AopConfigException("Cannot add advisor: Configuration is frozen.");
366                }
367                if (pos > this.advisors.size()) {
368                        throw new IllegalArgumentException(
369                                        "Illegal position " + pos + " in advisor list with size " + this.advisors.size());
370                }
371                this.advisors.add(pos, advisor);
372                updateAdvisorArray();
373                adviceChanged();
374        }
375
376        /**
377         * Bring the array up to date with the list.
378         */
379        protected final void updateAdvisorArray() {
380                this.advisorArray = this.advisors.toArray(new Advisor[this.advisors.size()]);
381        }
382
383        /**
384         * Allows uncontrolled access to the {@link List} of {@link Advisor Advisors}.
385         * <p>Use with care, and remember to {@link #updateAdvisorArray() refresh the advisor array}
386         * and {@link #adviceChanged() fire advice changed events} when making any modifications.
387         */
388        protected final List<Advisor> getAdvisorsInternal() {
389                return this.advisors;
390        }
391
392
393        @Override
394        public void addAdvice(Advice advice) throws AopConfigException {
395                int pos = this.advisors.size();
396                addAdvice(pos, advice);
397        }
398
399        /**
400         * Cannot add introductions this way unless the advice implements IntroductionInfo.
401         */
402        @Override
403        public void addAdvice(int pos, Advice advice) throws AopConfigException {
404                Assert.notNull(advice, "Advice must not be null");
405                if (advice instanceof IntroductionInfo) {
406                        // We don't need an IntroductionAdvisor for this kind of introduction:
407                        // It's fully self-describing.
408                        addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));
409                }
410                else if (advice instanceof DynamicIntroductionAdvice) {
411                        // We need an IntroductionAdvisor for this kind of introduction.
412                        throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor");
413                }
414                else {
415                        addAdvisor(pos, new DefaultPointcutAdvisor(advice));
416                }
417        }
418
419        @Override
420        public boolean removeAdvice(Advice advice) throws AopConfigException {
421                int index = indexOf(advice);
422                if (index == -1) {
423                        return false;
424                }
425                else {
426                        removeAdvisor(index);
427                        return true;
428                }
429        }
430
431        @Override
432        public int indexOf(Advice advice) {
433                Assert.notNull(advice, "Advice must not be null");
434                for (int i = 0; i < this.advisors.size(); i++) {
435                        Advisor advisor = this.advisors.get(i);
436                        if (advisor.getAdvice() == advice) {
437                                return i;
438                        }
439                }
440                return -1;
441        }
442
443        /**
444         * Is the given advice included in any advisor within this proxy configuration?
445         * @param advice the advice to check inclusion of
446         * @return whether this advice instance is included
447         */
448        public boolean adviceIncluded(Advice advice) {
449                if (advice != null) {
450                        for (Advisor advisor : this.advisors) {
451                                if (advisor.getAdvice() == advice) {
452                                        return true;
453                                }
454                        }
455                }
456                return false;
457        }
458
459        /**
460         * Count advices of the given class.
461         * @param adviceClass the advice class to check
462         * @return the count of the interceptors of this class or subclasses
463         */
464        public int countAdvicesOfType(Class<?> adviceClass) {
465                int count = 0;
466                if (adviceClass != null) {
467                        for (Advisor advisor : this.advisors) {
468                                if (adviceClass.isInstance(advisor.getAdvice())) {
469                                        count++;
470                                }
471                        }
472                }
473                return count;
474        }
475
476
477        /**
478         * Determine a list of {@link org.aopalliance.intercept.MethodInterceptor} objects
479         * for the given method, based on this configuration.
480         * @param method the proxied method
481         * @param targetClass the target class
482         * @return a List of MethodInterceptors (may also include InterceptorAndDynamicMethodMatchers)
483         */
484        public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) {
485                MethodCacheKey cacheKey = new MethodCacheKey(method);
486                List<Object> cached = this.methodCache.get(cacheKey);
487                if (cached == null) {
488                        cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
489                                        this, method, targetClass);
490                        this.methodCache.put(cacheKey, cached);
491                }
492                return cached;
493        }
494
495        /**
496         * Invoked when advice has changed.
497         */
498        protected void adviceChanged() {
499                this.methodCache.clear();
500        }
501
502        /**
503         * Call this method on a new instance created by the no-arg constructor
504         * to create an independent copy of the configuration from the given object.
505         * @param other the AdvisedSupport object to copy configuration from
506         */
507        protected void copyConfigurationFrom(AdvisedSupport other) {
508                copyConfigurationFrom(other, other.targetSource, new ArrayList<Advisor>(other.advisors));
509        }
510
511        /**
512         * Copy the AOP configuration from the given AdvisedSupport object,
513         * but allow substitution of a fresh TargetSource and a given interceptor chain.
514         * @param other the AdvisedSupport object to take proxy configuration from
515         * @param targetSource the new TargetSource
516         * @param advisors the Advisors for the chain
517         */
518        protected void copyConfigurationFrom(AdvisedSupport other, TargetSource targetSource, List<Advisor> advisors) {
519                copyFrom(other);
520                this.targetSource = targetSource;
521                this.advisorChainFactory = other.advisorChainFactory;
522                this.interfaces = new ArrayList<Class<?>>(other.interfaces);
523                for (Advisor advisor : advisors) {
524                        if (advisor instanceof IntroductionAdvisor) {
525                                validateIntroductionAdvisor((IntroductionAdvisor) advisor);
526                        }
527                        Assert.notNull(advisor, "Advisor must not be null");
528                        this.advisors.add(advisor);
529                }
530                updateAdvisorArray();
531                adviceChanged();
532        }
533
534        /**
535         * Build a configuration-only copy of this AdvisedSupport,
536         * replacing the TargetSource.
537         */
538        AdvisedSupport getConfigurationOnlyCopy() {
539                AdvisedSupport copy = new AdvisedSupport();
540                copy.copyFrom(this);
541                copy.targetSource = EmptyTargetSource.forClass(getTargetClass(), getTargetSource().isStatic());
542                copy.advisorChainFactory = this.advisorChainFactory;
543                copy.interfaces = this.interfaces;
544                copy.advisors = this.advisors;
545                copy.updateAdvisorArray();
546                return copy;
547        }
548
549
550        //---------------------------------------------------------------------
551        // Serialization support
552        //---------------------------------------------------------------------
553
554        private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
555                // Rely on default serialization; just initialize state after deserialization.
556                ois.defaultReadObject();
557
558                // Initialize transient fields.
559                initMethodCache();
560        }
561
562
563        @Override
564        public String toProxyConfigString() {
565                return toString();
566        }
567
568        /**
569         * For debugging/diagnostic use.
570         */
571        @Override
572        public String toString() {
573                StringBuilder sb = new StringBuilder(getClass().getName());
574                sb.append(": ").append(this.interfaces.size()).append(" interfaces ");
575                sb.append(ClassUtils.classNamesToString(this.interfaces)).append("; ");
576                sb.append(this.advisors.size()).append(" advisors ");
577                sb.append(this.advisors).append("; ");
578                sb.append("targetSource [").append(this.targetSource).append("]; ");
579                sb.append(super.toString());
580                return sb.toString();
581        }
582
583
584        /**
585         * Simple wrapper class around a Method. Used as the key when
586         * caching methods, for efficient equals and hashCode comparisons.
587         */
588        private static final class MethodCacheKey implements Comparable<MethodCacheKey> {
589
590                private final Method method;
591
592                private final int hashCode;
593
594                public MethodCacheKey(Method method) {
595                        this.method = method;
596                        this.hashCode = method.hashCode();
597                }
598
599                @Override
600                public boolean equals(Object other) {
601                        return (this == other || (other instanceof MethodCacheKey &&
602                                        this.method == ((MethodCacheKey) other).method));
603                }
604
605                @Override
606                public int hashCode() {
607                        return this.hashCode;
608                }
609
610                @Override
611                public String toString() {
612                        return this.method.toString();
613                }
614
615                @Override
616                public int compareTo(MethodCacheKey other) {
617                        int result = this.method.getName().compareTo(other.method.getName());
618                        if (result == 0) {
619                                result = this.method.toString().compareTo(other.method.toString());
620                        }
621                        return result;
622                }
623        }
624
625}