/* * Copyright 2008-2009 MOPAS(Ministry of Public Administration and Security). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package egovframework.rte.fdl.cmmn.aspect; import java.util.Locale; import egovframework.rte.fdl.cmmn.exception.BaseException; import egovframework.rte.fdl.cmmn.exception.EgovBizException; import egovframework.rte.fdl.cmmn.exception.FdlException; import egovframework.rte.fdl.cmmn.exception.manager.ExceptionHandlerService; import javax.annotation.Resource; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.dao.DataAccessException; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; /** * Exception 발생시 AOP(after-throwing) 에 의해 후처리로직 연결고리 역할 수행하는 클래스이다. * * <p><b>NOTE:</b> Exception 종류를 EgovBizException, RuntimeException(DataAccessException 포함), FdlException , * 나머지 Exception 으로 나누고 있으며, 후처리로직은 EgovBizException, RuntimeException 에서만 동작한다. * 그리고 나머지 Exception 의 경우 Exception 을 BaseException (메세지: fail.common.msg)으로 재생성하여 변경 던진다. * 따라서 fail.common.msg 메세지키가 Message Resource 에 정의 되어 있어야 한다.</p> * * @author Judd Cho (horanghi@gmail.com) * @since 2009.06.01 * @version 1.0 * @see * * <pre> * << 개정이력(Modification Information) >> * * 수정일 수정자 수정내용 * ------- -------- --------------------------- * 2009.05.30 Judd Cho 최초 생성 * 2015.01.31 Vincent Han 코드 품질 개선 및 보완 (processHandling 메소드 Exception 처리) * * </pre> */ public class ExceptionTransfer { private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionTransfer.class); @Resource(name = "messageSource") private MessageSource messageSource; private ExceptionHandlerService[] exceptionHandlerServices; /** * 디볼트로 패턴 매칭은 ANT 형태로 비교한다. */ private PathMatcher pm = new AntPathMatcher(); /** * ExceptionHandlerService을 여러개 지정한다. * * @param exceptionHandlerServices array of HandlerService */ public void setExceptionHandlerService(ExceptionHandlerService[] exceptionHandlerServices) { this.exceptionHandlerServices = exceptionHandlerServices; if (this.exceptionHandlerServices != null) { LOGGER.debug("count of ExceptionHandlerServices = {}", exceptionHandlerServices.length); } } /** * ExceptionHandlerService을 여러개 지정한다. * * @return int ExceptionHandlerService 갯수 */ public int countOfTheExceptionHandlerService() { return exceptionHandlerServices != null ? exceptionHandlerServices.length : 0; } /** * 발생한 Exception 에 따라 후처리 로직이 실행할 수 있도록 연결하는 역할을 수행한다. * * @param thisJoinPoint joinPoint 객체 * @param exception 발생한 Exception */ public void transfer(JoinPoint thisJoinPoint, Exception exception) throws Exception { LOGGER.debug("execute ExceptionTransfer.transfer "); Class<?> clazz = thisJoinPoint.getTarget().getClass(); Signature signature = thisJoinPoint.getSignature(); Locale locale = LocaleContextHolder.getLocale(); /* * BizException 을 구분하여 후처리로직을 수행하려 했으나 고려해야 할 부분이 발생. * Exception 구분하여 후처리 로직을 발생하려면 설정상에 Exception의 상세 설정이 필요하게된다. * 하지만 실제 현장에서 그렇게 나누는 경우는 없다. * 클래스 정보로만 패턴 분석을 통해 Handler 로 연결해주는 고리 역할 수행을 하게 된다. */ //EgovBizException 이 발생시 if (exception instanceof EgovBizException) { LOGGER.debug("Exception case :: EgovBizException "); EgovBizException be = (EgovBizException) exception; //wrapp 된 Exception 있는 경우 error 원인으로 출력해준다. if (be.getWrappedException() != null) { Throwable throwable = be.getWrappedException(); getLog(clazz).error(be.getMessage(), throwable); } else { getLog(clazz).error(be.getMessage(), be.getCause()); } // Exception Handler 에 발생된 Package 와 Exception 설정. (runtime 이 아닌 ExceptionHandlerService를 실행함) processHandling(clazz, signature.getName(), be, pm, exceptionHandlerServices); throw be; //RuntimeException 이 발생시 내부에서 DataAccessException 인 경우 는 별도록 throw 하고 있다. } else if (exception instanceof RuntimeException) { LOGGER.debug("RuntimeException case :: RuntimeException "); RuntimeException be = (RuntimeException) exception; getLog(clazz).error(be.getMessage(), be.getCause()); // Exception Handler 에 발생된 Package 와 Exception 설정. processHandling(clazz, signature.getName(), exception, pm, exceptionHandlerServices); if (be instanceof DataAccessException) { LOGGER.debug("RuntimeException case :: DataAccessException "); DataAccessException sqlEx = (DataAccessException) be; throw sqlEx; } throw be; //실행환경 확장모듈에서 발생한 Exception (요청: 공통모듈) :: 후처리로직 실행하지 않음. } else if (exception instanceof FdlException) { LOGGER.debug("FdlException case :: FdlException "); FdlException fe = (FdlException) exception; getLog(clazz).error(fe.getMessage(), fe.getCause()); throw fe; } else { //그외에 발생한 Exception 을 BaseException (메세지: fail.common.msg) 로 만들어 변경 던진다. //:: 후처리로직 실행하지 않음. LOGGER.debug("case :: Exception "); getLog(clazz).error(exception.getMessage(), exception.getCause()); throw processException(clazz, "fail.common.msg", new String[] {}, exception, locale); } } /** * Logger 얻기. * * @param clazz the returned logger will be named after clazz * @return logger */ protected Logger getLog(Class<?> clazz) { return LoggerFactory.getLogger(clazz); } /** * 발생한 Exception 에 따라 후처리 로직이 실행할 수 있도록 연결하는 역할을 수행한다. * * @param clazz Exception 발생 클래스 * @param msgKey Message key * @param msgArgs Message arguments * @param e Exception * @param locale Locale * @return 메시지 처리된 Exception */ protected Exception processException(final Class<?> clazz, final String msgKey, final String[] msgArgs, final Exception e, final Locale locale) { return processException(clazz, msgKey, msgArgs, e, locale, null); } /** * 발생한 Exception 에 따라 후처리 로직이 실행할 수 있도록 연결하는 역할을 수행한다. * * @param clazz Exception 발생 클래스 * @param msgKey Message key * @param msgArgs Message arguments * @param e Exception * @param locale Locale * @param ec ExceptionCreator * @return 메시지 처리된 Exception */ protected Exception processException(final Class<?> clazz, final String msgKey, final String[] msgArgs, final Exception e, final Locale locale, final ExceptionCreator ec) { getLog(clazz).error(messageSource.getMessage(msgKey, msgArgs, locale), e); ExceptionCreator exceptionCreator = null; if (ec == null) { exceptionCreator = new ExceptionCreator() { public Exception processException(MessageSource messageSource) { return new BaseException(messageSource, msgKey, msgArgs, locale, e); } }; } else { exceptionCreator = ec; } return exceptionCreator.processException(messageSource); } /** * Exception 생성 인터페이스이다. */ protected interface ExceptionCreator { Exception processException(MessageSource messageSource); } /** * 발생한 Exception 에 따라 후처리 로직이 실행할 수 있도록 연결하는 역할을 수행한다. * * @param clazz Exception 발생 클래스 * @param methodName Exception 발생 메소드명 * @param exception 발생한 Exception * @param pm 발생한 PathMatcher(default : AntPathMatcher) * @param exceptionHandlerServices[] 등록되어 있는 ExceptionHandlerService 리스트 */ protected void processHandling(Class<?> clazz, String methodName, Exception exception, PathMatcher pm, ExceptionHandlerService[] exceptionHandlerServices) { for (ExceptionHandlerService ehm : exceptionHandlerServices) { try { if (!ehm.hasReqExpMatcher()) { ehm.setReqExpMatcher(pm); } ehm.setPackageName(clazz.getCanonicalName() + "." + methodName); ehm.run(exception); } catch (Exception e) { LOGGER.error("ExceptionHandlerService Error", e); } } } }