View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.exception;
17  
18  import java.io.PrintWriter;
19  import java.io.StringWriter;
20  import java.net.UnknownHostException;
21  import java.util.HashMap;
22  import java.util.Map;
23  
24  import org.apache.log4j.Logger;
25  import org.kuali.rice.core.api.CoreApiServiceLocator;
26  import org.kuali.rice.core.api.exception.KualiException;
27  
28  /**
29   * Contains the exception incident information, exception, form and
30   * session user. It is constructed and saved into the HTTP Request for passing to the
31   * jsp when an exception occurs.
32   *
33   * @author Kuali Rice Team (rice.collab@kuali.org)
34   */
35  public class ExceptionIncident implements KualiExceptionIncident {
36      private static final Logger LOG = Logger.getLogger(ExceptionIncident.class);
37      public static final String GENERIC_SYSTEM_ERROR_MESSAGE = "The system has" +
38      		" encountered an error and is unable to complete your request at this time."+
39              " Please provide more information regarding this error by completing"+
40              " this Incident Report.";
41  
42      /**
43       * Basic exception information is initialized and contained in this class instance.
44       * Additional setting and other information can be added when exception is caught.
45       * Also, an JSP is displayed to collect additional user information and the returned
46       * parameters from the JSP can be used to initialize this class instance for
47       * reporting.
48       * <p>Note: The mechanism for passing data to and receiving data from the JSP uses
49       * java.util.Map. Therefore, the exception is not passed to JSP using HttpRequest.
50       * But rather a Map instance.
51       */
52      protected Map<String, String> properties=new HashMap<String, String>();
53  
54      /**
55       * This constructs list of key-value pairs from the caught exception and current
56       * settings.
57       *
58       * @param exception Caught exception
59       * @param properties Input information when the exception is caught
60       * <p>Example:
61       * <ul>
62       * <li>DOCUMENT_ID</li>
63       * <li>USER_EMAIL</li>
64       * <li>USER_NAME</li>
65       * <li>COMPONENT_NAME</li>
66       * </ul>
67       */
68      public ExceptionIncident(Exception exception,
69              Map<String,String> properties) {
70          if (LOG.isTraceEnabled()) {
71              String message=String.format("ENTRY %s%n%s",
72                      (exception==null)?"null":exception.toString(),
73                      (properties==null)?"":properties.toString());
74              LOG.trace(message);
75          }
76  
77          initialize(exception, properties);
78  
79          if (LOG.isTraceEnabled()) {
80              String message=String.format("EXIT %s", this.properties);
81              LOG.trace(message);
82          }
83  
84      }
85  
86      /**
87       * This constructs an instance of this class from list of name-value pairs.
88       *
89       * @param inputs List of exception information such as
90       * <ul>
91       * <li>DOCUMENT_ID - If it's document form</li>
92       * <li>USER_EMAIL - Session user email</li>
93       * <li>USER_NAME - Session user name</li>
94       * <li>COMPONENT_NAME - Document or lookup or inquiry form</li>
95       * attribute of GlobalVariables</li>
96       * <li>EXCEPTION_REPORT_SUBJECT - Exception error message and current settings</li>
97       * <li>EXCEPTION_MESSAGE - Exception error message</li>
98       * <li>STACK_TRACE - Exception stack trace</li>
99       * <li>DESCRIPTION - Information input by user or blank</li>
100      * </ul>
101      */
102     public ExceptionIncident(Map<String, String> inputs) {
103 
104         this.properties=inputs;
105 
106     }
107 
108     /**
109      * This method create and populate the internal properties parameter.
110      *
111      * @param thrownException The caught exception
112      * @param inputs Input information when the exception is caught
113      * <p>Example:
114      * <ul>
115      * <li>DOCUMENT_ID</li>
116      * <li>USER_EMAIL</li>
117      * <li>USER_NAME</li>
118      * <li>COMPONENT_NAME</li>
119      * </ul>
120      */
121     private void initialize(Exception thrownException, Map<String, String> inputs) {
122         if (LOG.isTraceEnabled()) {
123             String lm=String.format("ENTRY %s%n%s",
124                     thrownException.getMessage(),
125                     (inputs==null)?"null":inputs.toString());
126             LOG.trace(lm);
127         }
128 
129         properties=new HashMap<String, String>();
130         // Add all inputs
131         if (inputs != null) {
132             properties.putAll(inputs);
133         }
134         // Add all exception information
135         properties.putAll(getExceptionInfo(thrownException));
136 
137         if (LOG.isTraceEnabled()) {
138             String lm=String.format("EXIT %s", properties.toString());
139             LOG.trace(lm);
140         }
141     }
142 
143     /**
144      * This method return list of required information provided by the caught exception.
145      *
146      * @return
147      * <p>Example:
148      * <code>
149      * exceptionSubject, Caught exception message and settings information
150      * exceptionMessage, Caught exception message
151      * displayMessage, Either exception error message or generic exception error message
152      * stackTrace, Exception stack trace here
153      * </code>
154      *
155      */
156     private Map<String, String> getExceptionInfo(Exception exception) {
157         if (LOG.isTraceEnabled()) {
158             String message=String.format("ENTRY");
159             LOG.trace(message);
160         }
161 
162         Map<String, String> map=new HashMap<String, String>();
163         map.put(EXCEPTION_REPORT_SUBJECT, createReportSubject(exception));
164         map.put(EXCEPTION_MESSAGE, exception.getMessage());
165         map.put(DISPLAY_MESSAGE, getDisplayMessage(exception));
166         map.put(STACK_TRACE, getExceptionStackTrace(exception));
167         if(exception instanceof KualiException){
168         	boolean hideIncidentReport = ((KualiException) exception).isHideIncidentReport();
169         	map.put(EXCEPTION_HIDE_INCIDENT_REPORT, String.valueOf(hideIncidentReport));
170         }else{
171         	map.put(EXCEPTION_HIDE_INCIDENT_REPORT, String.valueOf(false));
172         }
173 
174         if (LOG.isTraceEnabled()) {
175             String message=String.format("ENTRY %s", map.toString());
176             LOG.trace(message);
177         }
178 
179         return map;
180     }
181 
182     /**
183      * This method compose the exception information that includes
184      * <ul>
185      * <li>environment - Application environment</li>
186      * <li>componentName- Document or lookup or inquiry form</li>
187      * <li>errorMessage - Exception error message</li>
188      * </ul>
189      * <p>Example;
190      * <code>
191      * kr-dev:SomeForm:Some error message
192      * </code>
193      *
194      * @param exception The caught exception
195      * @return report subject
196      */
197     private String createReportSubject(Exception exception) {
198         if (LOG.isTraceEnabled()) {
199             String lm=String.format("ENTRY");
200             LOG.trace(lm);
201         }
202         String app = CoreApiServiceLocator.getKualiConfigurationService().
203 				getPropertyValueAsString("application.id");
204         String env= CoreApiServiceLocator.getKualiConfigurationService().
205                 getPropertyValueAsString("environment");
206         String format="%s:%s:%s:%s";
207         String componentName=properties.get(COMPONENT_NAME);
208         String subject=String.format(format,
209         		app,
210                 env,
211                 (componentName==null)?"":componentName,
212                 exception.getMessage());
213 
214         if (LOG.isTraceEnabled()) {
215             String lm=String.format("EXIT %s", subject);
216             LOG.trace(lm);
217         }
218 
219         return subject;
220     }
221 
222     /**
223      * This method compose the exception information that includes
224      * <ul>
225      * <li>documentId - If it's document form</li>
226      * <li>userEmail - Session user email</li>
227      * <li>userName - Session user name</li>
228      * <li>component - Document or lookup or inquiry form</li>
229      * <li>description - Information input by user or blank</li>
230      * <li>errorMessage - Exception error message</li>
231      * <li>stackTrace - Exception stack trace</li>
232      * </ul>
233      * <p>Example;
234      * <code>
235      * documentId: 2942084
236      * userEmail: someone@somewhere
237      * userName: some name
238      * description: Something went wrong!
239      * component: document
240      * errorMessage: Some error message
241      * stackTrace: Exception stack trace here
242      * </code>
243      *
244      * @return report message body
245      */
246     private String createReportMessage() {
247         if (LOG.isTraceEnabled()) {
248             String lm=String.format("ENTRY");
249             LOG.trace(lm);
250         }
251         //KULRICE-12280: Adding server info to the message of the report
252         java.net.InetAddress addr = null;
253         try {
254             addr = java.net.InetAddress.getLocalHost();
255         } catch (UnknownHostException e) {
256             LOG.warn("Unable to get the localHost to inclue in exeception incident", e);
257         }
258         String documentId=properties.get(DOCUMENT_ID);
259         String userEmail=properties.get(USER_EMAIL);
260         String uuid=properties.get(UUID);
261         String description=properties.get(DESCRIPTION);
262         String component=properties.get(COMPONENT_NAME);
263         String serverInfo = addr.toString();
264         String exceptionMessage=properties.get(EXCEPTION_MESSAGE);
265         String stackTrace=properties.get(STACK_TRACE);
266         String format="Document Id: %s%n"+
267                       "User Email: %s%n"+
268                       "Person User Identifier: %s%n"+
269                       "User Input: %s%n"+
270                       "component: %s%n"+
271                       "Server Info: %s%n"+
272                       "errorMessage: %s%n"+
273                       "%s%n";
274         String message=String.format(format,
275                 (documentId==null)?"":documentId,
276                 (userEmail==null)?"":userEmail,
277                 (uuid==null)?"":uuid,
278                 (description==null)?"":description,
279                 (component==null)?"":component,
280                 (serverInfo==null)?"":serverInfo,
281                 (exceptionMessage==null)?"":exceptionMessage,
282                 (stackTrace==null)?"":stackTrace);
283 
284         if (LOG.isTraceEnabled()) {
285             String lm=String.format("EXIT %s", message);
286             LOG.trace(lm);
287         }
288 
289         return message;
290     }
291 
292     /**
293      * This method return the thrown exception stack trace as string.
294      *
295      * @param thrownException
296      * @return stack trace
297      */
298     public String getExceptionStackTrace(Exception thrownException) {
299         if (LOG.isTraceEnabled()) {
300             String lm=String.format("ENTRY");
301             LOG.trace(lm);
302         }
303 
304         StringWriter wrt=new StringWriter();
305         PrintWriter pw=new PrintWriter(wrt);
306         thrownException.printStackTrace(pw);
307         pw.flush();
308         String stackTrace=wrt.toString();
309         try {
310             wrt.close();
311             pw.close();
312         } catch (Exception e) {
313             LOG.trace(e.getMessage(), e);
314         }
315 
316         if (LOG.isTraceEnabled()) {
317             String lm=String.format("EXIT %s", stackTrace);
318             LOG.trace(lm);
319         }
320 
321         return stackTrace;
322     }
323 
324     /**
325      * This overridden method return the exception if the ixception type is in the
326      * defined list. Otherwise, it returns the GENERIC_SYSTEM_ERROR_MESSAGE.
327      *
328      * @see org.kuali.rice.krad.exception.KualiExceptionIncident#getDisplayMessage(Exception)
329      */
330     public String getDisplayMessage(Exception exception) {
331         if (LOG.isTraceEnabled()) {
332             String message=String.format("ENTRY %s", exception.getMessage());
333             LOG.trace(message);
334         }
335 
336         // Create the display message
337         String displayMessage;
338         if (exception instanceof KualiException) {
339             displayMessage=exception.getMessage();
340         } else {
341             //KULRICE:12361-Added a more detailed message on exception incidents
342             displayMessage=GENERIC_SYSTEM_ERROR_MESSAGE + "<br> Error Details: " + exception.getMessage();
343         }
344 
345         if (LOG.isTraceEnabled()) {
346             String message=String.format("EXIT %s", displayMessage);
347             LOG.trace(message);
348         }
349 
350         return displayMessage;
351     }
352 
353     /**
354      * This overridden method returns value of the found property key. Except the
355      * property EXCEPTION_REPORT_MESSAGE
356      *
357      * @see org.kuali.rice.krad.exception.KualiExceptionIncident#getProperty(java.lang.String)
358      */
359     public String getProperty(String key) {
360         if (LOG.isTraceEnabled()) {
361             String message=String.format("ENTRY %s", key);
362             LOG.trace(message);
363         }
364 
365         String value;
366         if (key.equals(EXCEPTION_REPORT_MESSAGE) && !properties.containsKey(key)) {
367             value=createReportMessage();
368             properties.put(EXCEPTION_REPORT_MESSAGE, value);
369         } else {
370             value=properties.get(key);
371         }
372 
373         if (LOG.isTraceEnabled()) {
374             String message=String.format("EXIT %s", value);
375             LOG.trace(message);
376         }
377 
378         return value;
379     }
380 
381     /**
382      * This overridden method return current internal properties.
383      *
384      * @see org.kuali.rice.krad.exception.KualiExceptionIncident#toProperties()
385      */
386     public Map<String, String> toProperties() {
387         if (LOG.isTraceEnabled()) {
388             String message=String.format("ENTRY");
389             LOG.trace(message);
390         }
391 
392         if (LOG.isTraceEnabled()) {
393             String message=String.format("EXIT %s", properties.toString());
394             LOG.trace(message);
395         }
396 
397         return properties;
398     }
399 }