001/*
002 * Copyright 2002-2020 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.dao.support;
018
019import java.util.Map;
020
021import org.aopalliance.intercept.MethodInterceptor;
022import org.aopalliance.intercept.MethodInvocation;
023
024import org.springframework.beans.BeansException;
025import org.springframework.beans.factory.BeanFactory;
026import org.springframework.beans.factory.BeanFactoryAware;
027import org.springframework.beans.factory.BeanFactoryUtils;
028import org.springframework.beans.factory.InitializingBean;
029import org.springframework.beans.factory.ListableBeanFactory;
030import org.springframework.lang.Nullable;
031import org.springframework.util.Assert;
032import org.springframework.util.ReflectionUtils;
033
034/**
035 * AOP Alliance MethodInterceptor that provides persistence exception translation
036 * based on a given PersistenceExceptionTranslator.
037 *
038 * <p>Delegates to the given {@link PersistenceExceptionTranslator} to translate
039 * a RuntimeException thrown into Spring's DataAccessException hierarchy
040 * (if appropriate). If the RuntimeException in question is declared on the
041 * target method, it is always propagated as-is (with no translation applied).
042 *
043 * @author Rod Johnson
044 * @author Juergen Hoeller
045 * @since 2.0
046 * @see PersistenceExceptionTranslator
047 */
048public class PersistenceExceptionTranslationInterceptor
049                implements MethodInterceptor, BeanFactoryAware, InitializingBean {
050
051        @Nullable
052        private volatile PersistenceExceptionTranslator persistenceExceptionTranslator;
053
054        private boolean alwaysTranslate = false;
055
056        @Nullable
057        private ListableBeanFactory beanFactory;
058
059
060        /**
061         * Create a new PersistenceExceptionTranslationInterceptor.
062         * Needs to be configured with a PersistenceExceptionTranslator afterwards.
063         * @see #setPersistenceExceptionTranslator
064         */
065        public PersistenceExceptionTranslationInterceptor() {
066        }
067
068        /**
069         * Create a new PersistenceExceptionTranslationInterceptor
070         * for the given PersistenceExceptionTranslator.
071         * @param pet the PersistenceExceptionTranslator to use
072         */
073        public PersistenceExceptionTranslationInterceptor(PersistenceExceptionTranslator pet) {
074                Assert.notNull(pet, "PersistenceExceptionTranslator must not be null");
075                this.persistenceExceptionTranslator = pet;
076        }
077
078        /**
079         * Create a new PersistenceExceptionTranslationInterceptor, autodetecting
080         * PersistenceExceptionTranslators in the given BeanFactory.
081         * @param beanFactory the ListableBeanFactory to obtaining all
082         * PersistenceExceptionTranslators from
083         */
084        public PersistenceExceptionTranslationInterceptor(ListableBeanFactory beanFactory) {
085                Assert.notNull(beanFactory, "ListableBeanFactory must not be null");
086                this.beanFactory = beanFactory;
087        }
088
089
090        /**
091         * Specify the PersistenceExceptionTranslator to use.
092         * <p>Default is to autodetect all PersistenceExceptionTranslators
093         * in the containing BeanFactory, using them in a chain.
094         * @see #detectPersistenceExceptionTranslators
095         */
096        public void setPersistenceExceptionTranslator(PersistenceExceptionTranslator pet) {
097                this.persistenceExceptionTranslator = pet;
098        }
099
100        /**
101         * Specify whether to always translate the exception ("true"), or whether throw the
102         * raw exception when declared, i.e. when the originating method signature's exception
103         * declarations allow for the raw exception to be thrown ("false").
104         * <p>Default is "false". Switch this flag to "true" in order to always translate
105         * applicable exceptions, independent from the originating method signature.
106         * <p>Note that the originating method does not have to declare the specific exception.
107         * Any base class will do as well, even {@code throws Exception}: As long as the
108         * originating method does explicitly declare compatible exceptions, the raw exception
109         * will be rethrown. If you would like to avoid throwing raw exceptions in any case,
110         * switch this flag to "true".
111         */
112        public void setAlwaysTranslate(boolean alwaysTranslate) {
113                this.alwaysTranslate = alwaysTranslate;
114        }
115
116        @Override
117        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
118                if (this.persistenceExceptionTranslator == null) {
119                        // No explicit exception translator specified - perform autodetection.
120                        if (!(beanFactory instanceof ListableBeanFactory)) {
121                                throw new IllegalArgumentException(
122                                                "Cannot use PersistenceExceptionTranslator autodetection without ListableBeanFactory");
123                        }
124                        this.beanFactory = (ListableBeanFactory) beanFactory;
125                }
126        }
127
128        @Override
129        public void afterPropertiesSet() {
130                if (this.persistenceExceptionTranslator == null && this.beanFactory == null) {
131                        throw new IllegalArgumentException("Property 'persistenceExceptionTranslator' is required");
132                }
133        }
134
135
136        @Override
137        public Object invoke(MethodInvocation mi) throws Throwable {
138                try {
139                        return mi.proceed();
140                }
141                catch (RuntimeException ex) {
142                        // Let it throw raw if the type of the exception is on the throws clause of the method.
143                        if (!this.alwaysTranslate && ReflectionUtils.declaresException(mi.getMethod(), ex.getClass())) {
144                                throw ex;
145                        }
146                        else {
147                                PersistenceExceptionTranslator translator = this.persistenceExceptionTranslator;
148                                if (translator == null) {
149                                        Assert.state(this.beanFactory != null,
150                                                        "Cannot use PersistenceExceptionTranslator autodetection without ListableBeanFactory");
151                                        translator = detectPersistenceExceptionTranslators(this.beanFactory);
152                                        this.persistenceExceptionTranslator = translator;
153                                }
154                                throw DataAccessUtils.translateIfNecessary(ex, translator);
155                        }
156                }
157        }
158
159        /**
160         * Detect all PersistenceExceptionTranslators in the given BeanFactory.
161         * @param bf the ListableBeanFactory to obtain PersistenceExceptionTranslators from
162         * @return a chained PersistenceExceptionTranslator, combining all
163         * PersistenceExceptionTranslators found in the given bean factory
164         * @see ChainedPersistenceExceptionTranslator
165         */
166        protected PersistenceExceptionTranslator detectPersistenceExceptionTranslators(ListableBeanFactory bf) {
167                // Find all translators, being careful not to activate FactoryBeans.
168                Map<String, PersistenceExceptionTranslator> pets = BeanFactoryUtils.beansOfTypeIncludingAncestors(
169                                bf, PersistenceExceptionTranslator.class, false, false);
170                ChainedPersistenceExceptionTranslator cpet = new ChainedPersistenceExceptionTranslator();
171                for (PersistenceExceptionTranslator pet : pets.values()) {
172                        cpet.addDelegate(pet);
173                }
174                return cpet;
175        }
176
177}