Coverage Report - net.admin4j.ui.filters.ErrorNotificationFilter
 
Classes in this File Line Coverage Branch Coverage Complexity
ErrorNotificationFilter
59%
61/102
42%
17/40
9.667
 
 1  
 /*
 2  
  * This software is licensed under the Apache License, Version 2.0
 3  
  * (the "License") agreement; you may not use this file except in compliance with
 4  
  * the License.  You may obtain a copy of the License at
 5  
  * 
 6  
  *      http://www.apache.org/licenses/LICENSE-2.0
 7  
  * 
 8  
  * Unless required by applicable law or agreed to in writing, software
 9  
  * distributed under the License is distributed on an "AS IS" BASIS,
 10  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 11  
  * See the License for the specific language governing permissions and
 12  
  * limitations under the License.
 13  
  */
 14  
 package net.admin4j.ui.filters;
 15  
 
 16  
 import java.io.IOException;
 17  
 import java.io.StringWriter;
 18  
 import java.util.HashMap;
 19  
 import java.util.List;
 20  
 import java.util.Map;
 21  
 import java.util.StringTokenizer;
 22  
 import java.util.concurrent.ConcurrentHashMap;
 23  
 
 24  
 import javax.servlet.Filter;
 25  
 import javax.servlet.FilterChain;
 26  
 import javax.servlet.FilterConfig;
 27  
 import javax.servlet.ServletException;
 28  
 import javax.servlet.ServletRequest;
 29  
 import javax.servlet.ServletResponse;
 30  
 import javax.servlet.http.HttpServletRequest;
 31  
 
 32  
 import net.admin4j.config.Admin4JConfiguration;
 33  
 import net.admin4j.deps.commons.lang3.StringUtils;
 34  
 import net.admin4j.deps.commons.lang3.exception.ExceptionUtils;
 35  
 import net.admin4j.entity.ExceptionInfo;
 36  
 import net.admin4j.entity.ExceptionInfoBase;
 37  
 import net.admin4j.util.Admin4jRuntimeException;
 38  
 import net.admin4j.util.ExpiringCache;
 39  
 import net.admin4j.util.FreemarkerUtils;
 40  
 import net.admin4j.util.GuiUtils;
 41  
 import net.admin4j.util.HostUtils;
 42  
 import net.admin4j.util.ServletUtils;
 43  
 import net.admin4j.util.notify.LogNotifier;
 44  
 import net.admin4j.vo.HttpRequestVO;
 45  
 
 46  
 import org.slf4j.Logger;
 47  
 import org.slf4j.LoggerFactory;
 48  
 
 49  
 import freemarker.template.Template;
 50  
 
 51  
 /**
 52  
  * Emails exceptions to a support email group.  
 53  
  * 
 54  
  * <p>This filter should be defined before all other filters (so it executes first/last).</p>
 55  
  * <p>Init parameters for this filter are as follows:</p>
 56  
  * <li>notifier -- Required.  Handles admin notification.  Can use the default Notifier setting.  
 57  
  * See documentation for the Notifier you're using
 58  
  * for any additional configuration requirements.  
 59  
  * For example, 'net.admin4j.util.notify.EmailNotifier'.</li>
 60  
  * <li>notification.time.interval.millis -- Optional.  If specified, notification 
 61  
  * will be provided for <i>like</i> errors once per specified time period.  For 
 62  
  * instance, if the interval is set to 10 minutes (600000), administrators will be notified 
 63  
  * once every 10 minutes for a Null Pointer Exception occurring on line 67 of class Foo for an http request.</li>
 64  
  * <li>exempted.exception.types -- Optional.  Comma delimited list of exception class names for which notification will *not* occur.</li>
 65  
  * <p>By default, all exceptions will be notified.</p>
 66  
  * @author D. Ashmore
 67  
  * @since 1.0
 68  
  */
 69  12
 public class ErrorNotificationFilter extends BaseNotificationFilter implements Filter {
 70  
     
 71  12
     private ExpiringCache exceptionCache = null;
 72  6
     private static Map<String,String> exemptedExceptionClassNames = new ConcurrentHashMap<String, String>();
 73  12
     private Logger configuredLogger = null;
 74  
         
 75  
         public void destroy() {
 76  
                 // NoOp
 77  
 
 78  0
         }
 79  
         
 80  
     /* (non-Javadoc)
 81  
      * @see net.admin4j.ui.filters.BaseNotificationFilter#init(javax.servlet.FilterConfig)
 82  
      */
 83  
     @Override
 84  
     public void init(FilterConfig config) throws ServletException {
 85  9
         super.init(config);
 86  
         
 87  0
         if (StringUtils.isEmpty(Admin4JConfiguration.getWebTransactionErrorLoggerName())) {
 88  9
             configuredLogger = logger;
 89  
         }
 90  0
         else configuredLogger = LoggerFactory.getLogger(Admin4JConfiguration.getWebTransactionErrorLoggerName());
 91  
         
 92  9
         Long timeInterval = null;
 93  9
         String timeIntervalStr = config.getInitParameter("notification.time.interval.millis");      
 94  0
         if ( !StringUtils.isEmpty(timeIntervalStr)) {            
 95  
             try {
 96  0
                 timeInterval = Long.parseLong(timeIntervalStr);
 97  0
             } catch (NumberFormatException e) {
 98  0
                 logger.warn("notification.time.interval.millis not numeric.  All errors will result in notification.", e);
 99  0
             }            
 100  
         }
 101  9
         else timeInterval = Admin4JConfiguration.getErrorNotificationTimeIntervalMillis();
 102  
         
 103  9
         if (timeInterval != null) {
 104  0
             exceptionCache = new ExpiringCache(timeInterval, timeInterval * 2);
 105  
         }
 106  
         else {
 107  9
             logger.info("All web application errors will result in notification.");
 108  
         }
 109  
         
 110  9
         if (exceptionCache != null) {
 111  0
             logger.info("Error Notification for Http Requests will occur once every {} ms for each type of error received.", exceptionCache.getExpirationTimeInMillis());
 112  
         }
 113  9
         else logger.info("Error Notification for Http Requests enabled.");
 114  
         
 115  9
         String exemptedExceptionStr = config.getInitParameter("exempted.exception.types");
 116  9
         String errorMessage = "Invalid exempted.exception.types parameter for ErrorNotificationFilter.  parm=";
 117  0
         if (StringUtils.isEmpty(exemptedExceptionStr)) {
 118  9
             exemptedExceptionStr = Admin4JConfiguration.getErrorExemptedExceptionTypes();
 119  9
             errorMessage = "Configuration item error.exempted.exception.types invalid.  item=";
 120  
         }
 121  
         
 122  0
         if ( !StringUtils.isEmpty(exemptedExceptionStr)) {
 123  
             try {
 124  0
                 StringTokenizer tok = new StringTokenizer(exemptedExceptionStr, ",");
 125  
                 String exemptedClassName;
 126  0
                 while (tok.hasMoreTokens()) {
 127  0
                     exemptedClassName = tok.nextToken();
 128  0
                     if (!StringUtils.isEmpty(exemptedClassName)) {
 129  0
                         exemptedExceptionClassNames.put(exemptedClassName, exemptedClassName);
 130  0
                         this.logger.info("Exceptions of type {} will not be notified.", exemptedClassName);
 131  
                     }
 132  
                 }
 133  
             }
 134  0
             catch (Throwable t) {
 135  0
                 this.logger.error( errorMessage + exemptedExceptionStr, 
 136  
                         t);
 137  0
             }
 138  
         }
 139  9
     }
 140  
 
 141  
         @SuppressWarnings("unchecked")
 142  
     public void doFilter(ServletRequest request, ServletResponse response,
 143  
                         FilterChain chain) throws IOException, ServletException {
 144  
                 try {
 145  18
                         chain.doFilter(request, response);
 146  
                 } 
 147  12
                 catch (Throwable t) {
 148  
                     
 149  12
                     boolean causeRethrownIntentionally = false;
 150  12
                     boolean notificationProvided = false;
 151  
                         
 152  
                     /*
 153  
                      * Under no circumstances should an Admin4J error 'mask' the underlying error for an application.
 154  
                      * Hence, all Admin4J work is surrounded by a try/catch making sure to throw the underlying
 155  
                      * exception up the chain.
 156  
                      */
 157  
                     try {
 158  
                             Throwable rootCause = ExceptionUtils.getRootCause(t);
 159  12
                             if (rootCause == null) {
 160  12
                                 rootCause = t;
 161  
                             }
 162  12
                             Throwable reportedCause = t;
 163  
                             
 164  12
                             logger.debug("Root Cause in Error Notification Filter", rootCause);
 165  
     
 166  12
                             if (exemptedExceptionClassNames.containsKey(rootCause.getClass().getName()))
 167  
                             {
 168  0
                                     logger.debug( "Exception type exempted.  No notification given", rootCause );
 169  0
                                     causeRethrownIntentionally = true;
 170  0
                                     ServletUtils.reThrowServletFilterException(t);
 171  
                             }
 172  
                             
 173  12
                             if (exceptionCache != null) {
 174  0
                                 synchronized (exceptionCache) {
 175  0
                                     ExceptionInfoBase eInfo = new ExceptionInfo(rootCause.getClass().getName(), rootCause.getStackTrace());
 176  0
                                     Long count = (Long) exceptionCache.get(eInfo);
 177  0
                                     if (count == null) {
 178  0
                                         exceptionCache.put(eInfo, Long.valueOf(1L));
 179  
                                     }
 180  
                                     else { // Notify already happened --> return.
 181  0
                                         exceptionCache.put(eInfo, Long.valueOf(count + 1));
 182  0
                                         causeRethrownIntentionally = true;
 183  0
                                         ServletUtils.reThrowServletFilterException(t);
 184  
                                     }
 185  0
                                 }
 186  
                             }
 187  
                             
 188  12
                             HttpServletRequest httpRequest = (HttpServletRequest)request;
 189  
                                              
 190  12
                     Map<String,Object> variableMap = new HashMap<String,Object>();
 191  12
                     variableMap.put("request", httpRequest);
 192  12
                     variableMap.put("errorType", rootCause.getClass().getName());
 193  12
                     if (rootCause.getMessage() == null) {
 194  0
                         variableMap.put("errorMessage", "null");
 195  
                     }
 196  12
                     else variableMap.put("errorMessage", "'" + rootCause.getMessage() + "'");
 197  
                     
 198  12
                     if (exceptionCache == null) {
 199  12
                         variableMap.put("suppressionIntervalMillis", Long.valueOf(0));
 200  
                     }
 201  0
                     else variableMap.put("suppressionIntervalMillis", Long.valueOf(this.exceptionCache.getExpirationTimeInMillis()));
 202  
                     
 203  12
                     List<HttpRequestVO> requestHistoryList = 
 204  
                             (List<HttpRequestVO>)httpRequest.getSession().getAttribute(
 205  
                         RequestTrackingFilter.REQUEST_TRACKING_SESSION_ATTRIBUTE_NAME);
 206  
 
 207  12
                     variableMap.put("GuiUtils", new GuiUtils());
 208  12
                     variableMap.put("host", HostUtils.getHostName());
 209  12
                     variableMap.put("requestHistory", requestHistoryList);
 210  12
                     variableMap.put("requestHistoryAttribute", 
 211  
                             RequestTrackingFilter.REQUEST_TRACKING_SESSION_ATTRIBUTE_NAME);
 212  
                     variableMap.put("rootCauseTrace", ExceptionUtils.getStackTrace(rootCause));
 213  
                     variableMap.put("reportedCauseTrace", ExceptionUtils.getStackTrace(reportedCause));
 214  12
                     variableMap.put("sessionAttributeList", ServletUtils.listSessionAttributes(httpRequest.getSession()));
 215  12
                     variableMap.put("requestAttributeList", ServletUtils.listRequestAttributes(httpRequest));
 216  
                     
 217  12
                     Template temp = null;
 218  12
                     if (this.notifier.supportsHtml()) {
 219  6
                         temp = FreemarkerUtils.createConfiguredTemplate(this.getClass(), "errorNotificationFilterHtml.ftl");
 220  
                     }
 221  
                     else {
 222  6
                         temp = FreemarkerUtils.createConfiguredTemplate(this.getClass(), "errorNotificationFilterText.ftl");
 223  
                     }
 224  
                     
 225  12
                     StringWriter message = new StringWriter();
 226  12
                     try {temp.process(variableMap, message);}
 227  0
                     catch (Exception e) {
 228  0
                         throw new Admin4jRuntimeException(e);
 229  12
                     }
 230  
     
 231  12
                             String errorMsg = null;
 232  12
                             if (rootCause.getMessage() != null) {
 233  12
                                     errorMsg = rootCause.getMessage().replace('\n', '-');
 234  
                             }
 235  
                             
 236  12
                             String subject = ServletUtils.getApplicationName(httpRequest) + 
 237  
                                     "@" + HostUtils.getHostName() + ": " + 
 238  
                                     rootCause.getClass() + ": " + errorMsg;
 239  12
                             this.notifier.notify(subject, message.toString());
 240  12
                             notificationProvided = true;
 241  
                     }
 242  0
                     catch (Throwable c) {
 243  0
                         if (!causeRethrownIntentionally) {
 244  0
                             logger.error("Error occured while providing error notification", c);
 245  
                         }
 246  
                     }
 247  
                     finally {
 248  12
                         if (!notificationProvided || (notificationProvided && !(this.notifier instanceof LogNotifier))) {
 249  12
                             configuredLogger.error("Error occured with web transaction", t);
 250  
                         }
 251  12
                         ServletUtils.reThrowServletFilterException(t);
 252  0
                     }
 253  6
                 }
 254  
 
 255  6
         }
 256  
 
 257  
 }