package org.fenixedu.bennu.portal.servlet;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import com.mitchellbosecke.pebble.error.LoaderException;
import com.mitchellbosecke.pebble.loader.ClasspathLoader;
/**
* This handler extends the default {@link PortalExceptionHandler}, by showing
* a page with more relevant information, when working in development mode.
*
* @author João Carvalho (joao.pedro.carvalho@tecnico.ulisboa.pt)
*
*/
public class PortalDevModeExceptionHandler extends PortalExceptionHandler {
public PortalDevModeExceptionHandler(final ServletContext context) {
super(new ClasspathLoader() {
@Override
public Reader getReader(String themeName) throws LoaderException {
return new InputStreamReader(context.getResourceAsStream("/bennu-portal/debugExceptionPage.html"),
StandardCharsets.UTF_8);
}
}, context);
}
@Override
protected void setExtraParameters(Map<String, Object> ctx, HttpServletRequest req, Throwable exception) {
ctx.put("threadName", Thread.currentThread().getName());
ctx.put("session", getSessionAttributes(req.getSession(false)));
ctx.put("throwableInfos", ThrowableInfo.getFlatThrowableInfoList(exception));
}
private Object getSessionAttributes(HttpSession session) {
if (session != null) {
Map<String, Object> attrs = new TreeMap<>();
Enumeration<String> names = session.getAttributeNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
try {
Object attribute = session.getAttribute(name);
attrs.put(name, attribute != null ? attribute.toString() : "");
} catch (Throwable t) {
attrs.put(name, "Unable to retrieve attribute due to exception " + t.getLocalizedMessage());
}
}
return attrs.entrySet();
}
return Collections.emptyList();
}
static class ThrowableInfo {
private final boolean cause;
private final boolean suppressed;
private final int level;
private final Throwable subject;
private final List<ElementInfo> subjectInfo;
public ThrowableInfo(boolean isCause, boolean isSurpressed, int level, Throwable subject) {
super();
this.cause = isCause;
this.suppressed = isSurpressed;
this.level = level;
this.subject = subject;
this.subjectInfo = getSubjectInfo(subject);
}
public boolean isCause() {
return cause;
}
public boolean isSuppressed() {
return suppressed;
}
public int getLevel() {
return level;
}
public Throwable getSubject() {
return subject;
}
public List<ElementInfo> getSubjectInfo() {
return subjectInfo;
}
public String getLocalizedMessage() {
String message = subject.getLocalizedMessage();
return message == null ? null : message.replace("\n", "<br />");
}
private static List<ElementInfo> getSubjectInfo(Throwable subject) {
List<ElementInfo> subjectInfo = new ArrayList<>();
for (StackTraceElement element : subject.getStackTrace()) {
subjectInfo.add(new ElementInfo(element));
}
return subjectInfo;
}
public static List<ThrowableInfo> getFlatThrowableInfoList(Throwable t) {
return getFlatThrowableInfoList(t, false, false, 0);
}
private static List<ThrowableInfo> getFlatThrowableInfoList(Throwable t, boolean isCause, boolean isSurpressed, int level) {
List<ThrowableInfo> list = new ArrayList<ThrowableInfo>();
list.add(new ThrowableInfo(isCause, isSurpressed, level, t));
for (Throwable supp : t.getSuppressed()) {
//suppressed are presented one level below
list.addAll(getFlatThrowableInfoList(supp, false, true, level + 1));
}
if (t.getCause() != null) {
//cause is presented at the same level
list.addAll(getFlatThrowableInfoList(t.getCause(), true, false, level));
}
return list;
}
}
static class ElementInfo {
private final StackTraceElement element;
private final boolean isExternalClass;
private final String simpleClassName;
private final String packageName;
public ElementInfo(StackTraceElement element) {
this.element = element;
String className = element.getClassName();
this.simpleClassName = getSimpleClassName(className);
this.packageName = className.substring(0, className.lastIndexOf("."));
this.isExternalClass = !(className.startsWith("pt.ist") || className.contains(".fenixedu."));
}
private String getSimpleClassName(String className) {
String[] parse = className.split("\\.");
return parse[parse.length - 1];
}
public StackTraceElement getElement() {
return element;
}
public boolean isExternalClass() {
return isExternalClass;
}
public String getSimpleClassName() {
return simpleClassName;
}
public String getPackageName() {
return packageName;
}
}
}