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