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}