001/**
002 * Copyright 2005-2018 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 java.io.PrintWriter;
019import java.io.StringWriter;
020import java.net.UnknownHostException;
021import java.util.HashMap;
022import java.util.Map;
023
024import org.apache.log4j.Logger;
025import org.kuali.rice.core.api.CoreApiServiceLocator;
026import org.kuali.rice.core.api.exception.KualiException;
027
028/**
029 * Contains the exception incident information, exception, form and
030 * session user. It is constructed and saved into the HTTP Request for passing to the
031 * jsp when an exception occurs.
032 *
033 * @author Kuali Rice Team (rice.collab@kuali.org)
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 properties Input information when the exception is caught
060     * <p>Example:
061     * <ul>
062     * <li>DOCUMENT_ID</li>
063     * <li>USER_EMAIL</li>
064     * <li>USER_NAME</li>
065     * <li>COMPONENT_NAME</li>
066     * </ul>
067     */
068    public ExceptionIncident(Exception exception,
069            Map<String,String> properties) {
070        if (LOG.isTraceEnabled()) {
071            String message=String.format("ENTRY %s%n%s",
072                    (exception==null)?"null":exception.toString(),
073                    (properties==null)?"":properties.toString());
074            LOG.trace(message);
075        }
076
077        initialize(exception, properties);
078
079        if (LOG.isTraceEnabled()) {
080            String message=String.format("EXIT %s", this.properties);
081            LOG.trace(message);
082        }
083
084    }
085
086    /**
087     * This constructs an instance of this class from list of name-value pairs.
088     *
089     * @param inputs List of exception information such as
090     * <ul>
091     * <li>DOCUMENT_ID - If it's document form</li>
092     * <li>USER_EMAIL - Session user email</li>
093     * <li>USER_NAME - Session user name</li>
094     * <li>COMPONENT_NAME - Document or lookup or inquiry form</li>
095     * attribute of GlobalVariables</li>
096     * <li>EXCEPTION_REPORT_SUBJECT - Exception error message and current settings</li>
097     * <li>EXCEPTION_MESSAGE - Exception error message</li>
098     * <li>STACK_TRACE - Exception stack trace</li>
099     * <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}