001 /** 002 * Copyright 2010 The Kuali Foundation Licensed under the 003 * Educational Community License, Version 2.0 (the "License"); you may 004 * not use this file except in compliance with the License. You may 005 * obtain a copy of the License at 006 * 007 * http://www.osedu.org/licenses/ECL-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, 010 * software distributed under the License is distributed on an "AS IS" 011 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 012 * or implied. See the License for the specific language governing 013 * permissions and limitations under the License. 014 */ 015 016 package org.kuali.student.common.util; 017 018 import java.io.PrintWriter; 019 import java.io.StringWriter; 020 import java.lang.reflect.Constructor; 021 import java.util.Map; 022 023 import org.apache.log4j.Logger; 024 import org.springframework.aop.ThrowsAdvice; 025 import org.springframework.core.Ordered; 026 027 /** 028 * @author Daniel Epstein 029 * <p> 030 * Use this Advice to map one exception to another for use when other 031 * advice eats your runtime exceptions outside of your code. This happens in 032 * Transactions when commit is not called until outside of your DAO 033 * layer. 034 * </p> 035 * 036 * <p> 037 * Set the property "exceptionMapping" as a map that maps an exception class to 038 * your own exception class 039 * </p> 040 * 041 * <p> 042 * Remember that aspect order is important and that this bean will always be 043 * order "500" 044 * </p> 045 * 046 * Example: 047 * 048 * <pre> 049 * <tx:annotation-driven transaction-manager="JtaTxManager" order="1000"/> 050 * lt;bean id="mapExceptionAdvisor" 051 * class="org.myfoo.ExceptionMappingAdvice"> 052 * <property name="exceptionMapping"> 053 * <map> 054 * <entry key="javax.persistence.EntityExistsException" 055 * value="org.myfoo.exceptions.AlreadyExistsException" /> 056 * </map> 057 * </property> 058 * lt;/bean> 059 * lt;aop:config> 060 * <aop:aspect id="dataAccessToBusinessException" 061 * ref="mapExceptionAdvisor"> 062 * <aop:after-throwing 063 * pointcut="execution(* org.myfoo.service.*.*(..))" 064 * method="afterThrowing" throwing="ex" /> 065 * </aop:aspect> 066 * lt;/aop:config> 067 * </pre> 068 */ 069 public class ExceptionMappingAdvice implements ThrowsAdvice, Ordered { 070 private int order = 500; 071 private static final long serialVersionUID = 1L; 072 private Map<Class<? extends Exception>, Class<? extends Exception>> exceptionMapping; 073 private Class<? extends Exception> defaultException; 074 final Logger logger = Logger.getLogger(ExceptionMappingAdvice.class); 075 076 /** 077 * This method will use the real exception thrown and look up the exception 078 * that should be thrown 079 * 080 * @param ex 081 * @throws Exception 082 */ 083 public void afterThrowing(Exception ex) throws Exception { 084 Class<? extends Exception> mappedExceptionClass = exceptionMapping 085 .get(ex.getClass()); 086 087 if (mappedExceptionClass != null) { 088 logger.debug("Mapping exception "+ex.getClass()+" to "+mappedExceptionClass); 089 Constructor<? extends Exception> c = mappedExceptionClass 090 .getConstructor(String.class); 091 Exception mappedException = c.newInstance(ex.getMessage()); 092 throw mappedException; 093 } 094 095 //Throw a default exception if this is a runtime exception 096 if(ex instanceof RuntimeException){ 097 logger.trace("No mapping available, throwing default exception "+defaultException); 098 if (defaultException != null) { 099 //Log the error 100 StringWriter traceWriter = new StringWriter(); 101 PrintWriter printWriter = new PrintWriter(traceWriter, false); 102 logger.error(printWriter, ex); 103 printWriter.close(); 104 String faultMessage = traceWriter.getBuffer().toString(); 105 logger.error(faultMessage); 106 //Throw the default exception 107 try{ 108 Constructor<? extends Exception> c = defaultException 109 .getConstructor(String.class, Throwable.class); 110 throw c.newInstance(ex.getMessage(), ex); 111 }catch(NoSuchMethodException e){ 112 Constructor<? extends Exception> c = defaultException 113 .getConstructor(String.class); 114 throw c.newInstance(ex.getMessage()); 115 } 116 } 117 //Check if no default was defined 118 logger.debug("No mapping or default exception available. Exception "+ex.getClass()); 119 throw new RuntimeException("Could Not Map Exception: " + ex.toString()); 120 } 121 } 122 123 @Override 124 public int getOrder() { 125 return order; 126 } 127 128 /** 129 * @param order 130 * the order to set 131 */ 132 public void setOrder(int order) { 133 this.order = order; 134 } 135 136 /** 137 * @return the exceptionMapping 138 */ 139 public Map<Class<? extends Exception>, Class<? extends Exception>> getExceptionMapping() { 140 return exceptionMapping; 141 } 142 143 /** 144 * @param exceptionMapping 145 * the exceptionMapping to set 146 */ 147 public void setExceptionMapping( 148 Map<Class<? extends Exception>, Class<? extends Exception>> exceptionMapping) { 149 this.exceptionMapping = exceptionMapping; 150 } 151 152 /** 153 * @return the defaultException 154 */ 155 public Class<? extends Exception> getDefaultException() { 156 return defaultException; 157 } 158 159 /** 160 * @param defaultException 161 * the defaultException to set 162 */ 163 public void setDefaultException(Class<? extends Exception> defaultException) { 164 this.defaultException = defaultException; 165 } 166 167 }