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     * &lt;tx:annotation-driven transaction-manager=&quot;JtaTxManager&quot; order=&quot;1000&quot;/&gt;
050     * lt;bean id=&quot;mapExceptionAdvisor&quot;
051     * class=&quot;org.myfoo.ExceptionMappingAdvice&quot;&gt;
052     * &lt;property name=&quot;exceptionMapping&quot;&gt;
053     *      &lt;map&gt;
054     *              &lt;entry key=&quot;javax.persistence.EntityExistsException&quot;
055     *                      value=&quot;org.myfoo.exceptions.AlreadyExistsException&quot; /&gt;
056     *      &lt;/map&gt;
057     * &lt;/property&gt;
058     * lt;/bean&gt;
059     * lt;aop:config&gt;
060     * &lt;aop:aspect id=&quot;dataAccessToBusinessException&quot;
061     *      ref=&quot;mapExceptionAdvisor&quot;&gt;
062     *      &lt;aop:after-throwing
063     *              pointcut=&quot;execution(* org.myfoo.service.*.*(..))&quot;
064     *              method=&quot;afterThrowing&quot; throwing=&quot;ex&quot; /&gt;
065     * &lt;/aop:aspect&gt;
066     * lt;/aop:config&gt;
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    }