001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.exception;
017
018import org.apache.log4j.Logger;
019import org.kuali.rice.core.api.exception.KualiException;
020import org.kuali.rice.krad.service.KRADServiceLocator;
021
022import java.io.PrintWriter;
023import java.io.StringWriter;
024import java.util.HashMap;
025import java.util.Map;
026
027/**
028 * This class contains the exception incident information, exception, form and
029 * session user. It is constructed and saved into the HTTP Request for passing to the
030 * jsp when an exception occurs. 
031 * 
032 * @author Kuali Rice Team (rice.collab@kuali.org)
033 *
034 */
035public class ExceptionIncident implements KualiExceptionIncident {
036    private static final Logger LOG = Logger.getLogger(ExceptionIncident.class);
037    public static final String GENERIC_SYSTEM_ERROR_MESSAGE = "The system has" +
038                " encountered an error and is unable to complete your request at this time."+
039            " Please provide more information regarding this error by completing"+
040            " this Incident Report.";
041
042    /**
043     * Basic exception information is initialized and contained in this class instance.
044     * Additional setting and other information can be added when exception is caught.
045     * Also, an JSP is displayed to collect additional user information and the returned
046     * parameters from the JSP can be used to initialize this class instance for
047     * reporting.
048     * <p>Note: The mechanism for passing data to and receiving data from the JSP uses
049     * java.util.Map. Therefore, the exception is not passed to JSP using HttpRequest.
050     * But rather a Map instance. 
051     */
052    protected Map<String, String> properties=new HashMap<String, String>();
053    
054    /**
055     * This constructs list of key-value pairs from the caught exception and current
056     * settings.
057     * 
058     * @param exception Caught exception
059     * @param exceptionNames List of Kuali exception for determining the display exception
060     * message
061     * @param properties Input information when the exception is caught
062     * <p>Example:
063     * <ul>
064     * <li>DOCUMENT_ID</li>
065     * <li>USER_EMAIL</li>
066     * <li>USER_NAME</li>
067     * <li>COMPONENT_NAME</li>
068     * </ul> 
069     */
070    public ExceptionIncident(Exception exception,
071            Map<String,String> properties) {
072        if (LOG.isTraceEnabled()) {
073            String message=String.format("ENTRY %s%n%s",
074                    (exception==null)?"null":exception.toString(),
075                    (properties==null)?"":properties.toString());
076            LOG.trace(message);
077        }
078        
079        initialize(exception, properties);
080                
081        if (LOG.isTraceEnabled()) {
082            String message=String.format("EXIT %s", this.properties);
083            LOG.trace(message);
084        }
085        
086    }
087    
088    /**
089     * This constructs an instance of this class from list of name-value pairs.
090     * 
091     * @param inputs List of exception information such as
092     * <ul>
093     * <li>DOCUMENT_ID - If it's document form</li>
094     * <li>USER_EMAIL - Session user email</li>
095     * <li>USER_NAME - Session user name</li>
096     * <li>COMPONENT_NAME - Document or lookup or inquiry form</li>
097     * attribute of GlobalVariables</li>
098     * <li>EXCEPTION_REPORT_SUBJECT - Exception error message and current settings</li>
099     * <li>EXCEPTION_MESSAGE - Exception error message</li>
100     * <li>STACK_TRACE - Exception stack trace</li>
101     * <li>DESCRIPTION - Information input by user or blank</li>
102     * </ul>
103     */
104    public ExceptionIncident(Map<String, String> inputs) {
105        
106        this.properties=inputs;
107        
108    }
109    
110    /**
111     * This method create and populate the internal properties parameter.
112     * 
113     * @param thrownException The caught exception
114     * @param inputs Input information when the exception is caught
115     * <p>Example:
116     * <ul>
117     * <li>DOCUMENT_ID</li>
118     * <li>USER_EMAIL</li>
119     * <li>USER_NAME</li>
120     * <li>COMPONENT_NAME</li>
121     * </ul> 
122     */
123    private void initialize(Exception thrownException, Map<String, String> inputs) {
124        if (LOG.isTraceEnabled()) {
125            String lm=String.format("ENTRY %s%n%s",
126                    thrownException.getMessage(),
127                    (inputs==null)?"null":inputs.toString());
128            LOG.trace(lm);
129        }
130
131        properties=new HashMap<String, String>();
132        // Add all inputs
133        if (inputs != null) {
134            properties.putAll(inputs);
135        }
136        // Add all exception information
137        properties.putAll(getExceptionInfo(thrownException));
138        
139        if (LOG.isTraceEnabled()) {
140            String lm=String.format("EXIT %s", properties.toString());
141            LOG.trace(lm);
142        }
143    }    
144
145    /**
146     * This method return list of required information provided by the caught exception.
147     * 
148     * @return
149     * <p>Example:
150     * <code>
151     * exceptionSubject, Caught exception message and settings information
152     * exceptionMessage, Caught exception message
153     * displayMessage, Either exception error message or generic exception error message
154     * stackTrace, Exception stack trace here
155     * </code>
156     * 
157     */
158    private Map<String, String> getExceptionInfo(Exception exception) {
159        if (LOG.isTraceEnabled()) {
160            String message=String.format("ENTRY");
161            LOG.trace(message);
162        }
163
164        Map<String, String> map=new HashMap<String, String>();
165        map.put(EXCEPTION_REPORT_SUBJECT, createReportSubject(exception));
166        map.put(EXCEPTION_MESSAGE, exception.getMessage());
167        map.put(DISPLAY_MESSAGE, getDisplayMessage(exception));
168        map.put(STACK_TRACE, getExceptionStackTrace(exception));
169        if(exception instanceof KualiException){
170                boolean hideIncidentReport = ((KualiException) exception).isHideIncidentReport();
171                map.put(EXCEPTION_HIDE_INCIDENT_REPORT, String.valueOf(hideIncidentReport));
172        }else{
173                map.put(EXCEPTION_HIDE_INCIDENT_REPORT, String.valueOf(false));
174        }
175
176        if (LOG.isTraceEnabled()) {
177            String message=String.format("ENTRY %s", map.toString());
178            LOG.trace(message);
179        }
180
181        return map;
182    }
183    
184    /**
185     * This method compose the exception information that includes
186     * <ul>
187     * <li>environment - Application environment</li>
188     * <li>componentName- Document or lookup or inquiry form</li>
189     * <li>errorMessage - Exception error message</li>
190     * </ul>
191     * <p>Example;
192     * <code>
193     * kr-dev:SomeForm:Some error message
194     * </code>
195     * 
196     * @param exception The caught exception
197     * @return
198     */
199    private String createReportSubject(Exception exception) {
200        if (LOG.isTraceEnabled()) {
201            String lm=String.format("ENTRY");
202            LOG.trace(lm);
203        }
204        String app = KRADServiceLocator.getKualiConfigurationService().
205                                getPropertyValueAsString("application.id");
206        String env= KRADServiceLocator.getKualiConfigurationService().
207                getPropertyValueAsString("environment");
208        String format="%s:%s:%s:%s";
209        String componentName=properties.get(COMPONENT_NAME);
210        String subject=String.format(format,
211                        app,
212                env,
213                (componentName==null)?"":componentName,
214                exception.getMessage());
215        
216        if (LOG.isTraceEnabled()) {
217            String lm=String.format("EXIT %s", subject);
218            LOG.trace(lm);
219        }
220        
221        return subject;
222    }
223    
224    /**
225     * This method compose the exception information that includes
226     * <ul>
227     * <li>documentId - If it's document form</li>
228     * <li>userEmail - Session user email</li>
229     * <li>userName - Session user name</li>
230     * <li>component - Document or lookup or inquiry form</li>
231     * <li>description - Information input by user or blank</li>
232     * <li>errorMessage - Exception error message</li>
233     * <li>stackTrace - Exception stack trace</li>
234     * </ul>
235     * <p>Example;
236     * <code>
237     * documentId: 2942084
238     * userEmail: someone@somewhere
239     * userName: some name
240     * description: Something went wrong!
241     * component: document
242     * errorMessage: Some error message
243     * stackTrace: Exception stack trace here 
244     * </code>
245     * 
246     * @return
247     */
248    private String createReportMessage() {
249        if (LOG.isTraceEnabled()) {
250            String lm=String.format("ENTRY");
251            LOG.trace(lm);
252        }
253                
254        String documentId=properties.get(DOCUMENT_ID);
255        String userEmail=properties.get(USER_EMAIL);
256        String uuid=properties.get(UUID);
257        String description=properties.get(DESCRIPTION);
258        String component=properties.get(COMPONENT_NAME);
259        String exceptionMessage=properties.get(EXCEPTION_MESSAGE);
260        String stackTrace=properties.get(STACK_TRACE);
261        String format="Document Id: %s%n"+
262                      "User Email: %s%n"+
263                      "Person User Identifier: %s%n"+
264                      "User Input: %s%n"+
265                      "component: %s%n"+
266                      "errorMessage: %s%n"+
267                      "%s%n";
268        String message=String.format(format,
269                (documentId==null)?"":documentId,
270                (userEmail==null)?"":userEmail,
271                (uuid==null)?"":uuid,
272                (description==null)?"":description,
273                (component==null)?"":component,
274                (exceptionMessage==null)?"":exceptionMessage,
275                (stackTrace==null)?"":stackTrace);
276
277        if (LOG.isTraceEnabled()) {
278            String lm=String.format("EXIT %s", message);
279            LOG.trace(lm);
280        }
281        
282        return message;
283    }
284
285    /**
286     * This method return the thrown exception stack trace as string.
287     * 
288     * @param thrownException
289     * @return
290     */
291    public String getExceptionStackTrace(Exception thrownException) {
292        if (LOG.isTraceEnabled()) {
293            String lm=String.format("ENTRY");
294            LOG.trace(lm);
295        }
296                
297        StringWriter wrt=new StringWriter();
298        PrintWriter pw=new PrintWriter(wrt);
299        thrownException.printStackTrace(pw);
300        pw.flush();
301        String stackTrace=wrt.toString();
302        try {
303            wrt.close();
304            pw.close();
305        } catch (Exception e) {
306            LOG.trace(e.getMessage(), e);
307        }
308        
309        if (LOG.isTraceEnabled()) {
310            String lm=String.format("EXIT %s", stackTrace);
311            LOG.trace(lm);
312        }
313        
314        return stackTrace;
315    }
316    
317    /**
318     * This overridden method return the exception if the ixception type is in the
319     * defined list. Otherwise, it returns the GENERIC_SYSTEM_ERROR_MESSAGE.
320     * 
321     * @see org.kuali.rice.krad.exception.KualiExceptionIncident#getDisplayMessage(Exception)
322     */
323    public String getDisplayMessage(Exception exception) {
324        if (LOG.isTraceEnabled()) {
325            String message=String.format("ENTRY %s", exception.getMessage());
326            LOG.trace(message);
327        }
328
329        // Create the display message
330        String displayMessage;
331        if (exception instanceof KualiException) {
332            displayMessage=exception.getMessage();
333        } else {
334            displayMessage=GENERIC_SYSTEM_ERROR_MESSAGE;
335        }
336
337        if (LOG.isTraceEnabled()) {
338            String message=String.format("EXIT %s", displayMessage);
339            LOG.trace(message);
340        }
341
342        return displayMessage;
343    }
344
345    /**
346     * This overridden method returns value of the found property key. Except the
347     * property EXCEPTION_REPORT_MESSAGE
348     * 
349     * @see org.kuali.rice.krad.exception.KualiExceptionIncident#getProperty(java.lang.String)
350     */
351    public String getProperty(String key) {
352        if (LOG.isTraceEnabled()) {
353            String message=String.format("ENTRY %s", key);
354            LOG.trace(message);
355        }
356        
357        String value;
358        if (key.equals(EXCEPTION_REPORT_MESSAGE) && !properties.containsKey(key)) {
359            value=createReportMessage();
360            properties.put(EXCEPTION_REPORT_MESSAGE, value);
361        } else {
362            value=properties.get(key);
363        }
364        
365        if (LOG.isTraceEnabled()) {
366            String message=String.format("EXIT %s", value);
367            LOG.trace(message);
368        }
369
370        return value;
371    }
372
373    /**
374     * This overridden method return current internal properties.
375     * 
376     * @see org.kuali.rice.krad.exception.KualiExceptionIncident#toProperties()
377     */
378    public Map<String, String> toProperties() {
379        if (LOG.isTraceEnabled()) {
380            String message=String.format("ENTRY");
381            LOG.trace(message);
382        }
383
384        if (LOG.isTraceEnabled()) {
385            String message=String.format("EXIT %s", properties.toString());
386            LOG.trace(message);
387        }
388
389        return properties;
390    }
391}