package org.fenixedu.bennu.portal.servlet; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.Reader; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.fenixedu.bennu.core.security.Authenticate; import org.fenixedu.bennu.core.servlet.ExceptionHandlerFilter.ExceptionHandler; import org.fenixedu.bennu.portal.BennuPortalConfiguration; import org.fenixedu.bennu.portal.domain.PortalConfiguration; import org.fenixedu.commons.i18n.I18N; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mitchellbosecke.pebble.PebbleEngine; import com.mitchellbosecke.pebble.PebbleEngine.Builder; import com.mitchellbosecke.pebble.error.LoaderException; import com.mitchellbosecke.pebble.error.PebbleException; import com.mitchellbosecke.pebble.loader.ClasspathLoader; import com.mitchellbosecke.pebble.loader.Loader; import com.mitchellbosecke.pebble.template.PebbleTemplate; /** * Custom {@link ExceptionHandler} that allows Portal themes to provide their own custom error pages. * * If the current theme does not define an error page, a default is shown. * * @author João Carvalho (joao.pedro.carvalho@tecnico.ulisboa.pt) * */ public class PortalExceptionHandler implements ExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(PortalExceptionHandler.class); private final PebbleEngine engine; public PortalExceptionHandler(final ServletContext context) { this(new ClasspathLoader() { @Override public Reader getReader(String themeName) throws LoaderException { // Try to resolve the page from the theme... InputStream stream = context.getResourceAsStream("/themes/" + themeName + "/500.html"); if (stream != null) { return new InputStreamReader(stream, StandardCharsets.UTF_8); } else { // ... and fall back if none is provided. return new InputStreamReader(context.getResourceAsStream("/bennu-portal/500.html"), StandardCharsets.UTF_8); } } }, context); } protected PortalExceptionHandler(Loader loader, ServletContext context) { this.engine = new Builder().loader(loader).extension(new PortalExtension(context)) .cacheActive(!BennuPortalConfiguration.getConfiguration().themeDevelopmentMode()).build(); } @Override public boolean handle(ServletRequest request, ServletResponse response, Throwable exception) throws ServletException, IOException { if (response.isCommitted()) { return false; } response.reset(); ((HttpServletResponse) response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); HttpServletRequest req = (HttpServletRequest) request; logger.error("Request at " + req.getRequestURI() + " threw an exception: ", exception); Map<String, Object> ctx = new HashMap<>(); PortalConfiguration config = PortalConfiguration.getInstance(); ctx.put("loggedUser", Authenticate.getUser()); ctx.put("config", config); ctx.put("contextPath", req.getContextPath()); ctx.put("request", req); ctx.put("exception", exception); ctx.put("locale", I18N.getLocale()); ctx.put("userAgent", req.getHeader("User-Agent")); ctx.put("referer", req.getHeader("Referer")); ctx.put("parameters", getParameters(req)); ctx.put("attributes", getAttributes(req)); ctx.put("functionality", BennuPortalDispatcher.getSelectedFunctionality(req)); setExtraParameters(ctx, req, exception); StringWriter writer = new StringWriter(1024); exception.printStackTrace(new PrintWriter(writer)); ctx.put("stackTrace", writer.toString()); try { response.setContentType("text/html;charset=UTF-8"); PebbleTemplate template = engine.getTemplate(config.getTheme()); template.evaluate(response.getWriter(), ctx, I18N.getLocale()); return true; } catch (PebbleException e) { throw new IOException(e); } } protected void setExtraParameters(Map<String, Object> ctx, HttpServletRequest req, Throwable exception) { // Do nothing by default } private Object getParameters(HttpServletRequest req) { Map<String, String> params = new TreeMap<>(); Enumeration<String> names = req.getParameterNames(); while (names.hasMoreElements()) { String name = names.nextElement(); params.put(name, String.join(" | ", req.getParameterValues(name))); } return params.entrySet(); } private Object getAttributes(HttpServletRequest req) { Map<String, Object> attrs = new TreeMap<>(); Enumeration<String> names = req.getAttributeNames(); while (names.hasMoreElements()) { String name = names.nextElement(); try { Object attribute = req.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(); } }