1

I am using struts-2.3.16 and I have to suppress exceptions from Freemarker template globally in our application. This means that, instead of the yellow screen with the stacktrace from Freemarker, I have to forward to a global jsp that displays a generic message, so preventing the display of stacktraces to the user. For generic exceptions in struts we mapped a global-results in struts.xml, but it's not working for Freemarker exceptions.

So far I have implemented the solution from What are different ways to handle error in FreeMarker template?. So I created a CustomFreemarkerManager and a CustomTemplateExceptionHandler.

My CustomFreemarkerManager looks like this:

@Override
public void init(ServletContext servletContext) throws TemplateException {
    super.config = super.createConfiguration(servletContext);
    super.config.setTemplateExceptionHandler(new CustomTemplateExceptionHandler(servletContext));
    super.contentType = "text/html";
    super.wrapper = super.createObjectWrapper(servletContext);
    if (LOG.isDebugEnabled()) {
        LOG.debug("Using object wrapper of class " + super.wrapper.getClass().getName(), new String[0]);
    }

    super.config.setObjectWrapper(super.wrapper);
    super.templatePath = servletContext.getInitParameter("TemplatePath");
    if (super.templatePath == null) {
        super.templatePath = servletContext.getInitParameter("templatePath");
    }

    super.configureTemplateLoader(super.createTemplateLoader(servletContext, super.templatePath));
    super.loadSettings(servletContext);
}

@Override
protected Configuration createConfiguration(ServletContext servletContext) throws TemplateException {
    Configuration configuration = new Configuration();
    configuration.setTemplateExceptionHandler(new CustomTemplateExceptionHandler(servletContext));
    if (super.mruMaxStrongSize > 0) {
        configuration.setSetting("cache_storage", "strong:" + super.mruMaxStrongSize);
    }

    if (super.templateUpdateDelay != null) {
        configuration.setSetting("template_update_delay", super.templateUpdateDelay);
    }

    if (super.encoding != null) {
        configuration.setDefaultEncoding(super.encoding);
    }

    configuration.setLocalizedLookup(false);
    configuration.setWhitespaceStripping(true);
    return configuration;
}

From here I send the ServletContext to my CustomTemplateExceptionHandler so I can create a RequestDispatcher to forward to my exception.jsp. The problem is that in the exception handler I don't have the request and the response and I can't forward to my jsp.

The class CustomTemplateExceptionHandler looks like this so far:

private ServletContext servletContext;

public CustomTemplateExceptionHandler(ServletContext servletContext) {
    this.servletContext = servletContext;
}

public void handleTemplateException(TemplateException te, Environment env, Writer out) throws TemplateException {
    if (servletContext != null) {
        RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher("/resources/exception.jsp");

        //HERE I have to forward to my jsp
    }
}

Anybody knows how can I do that? I want the stacktrace to be logged only on the server, and in the UI to replace the stacktrace with a generic message.

Community
  • 1
  • 1
  • I have modified now the code to rethrow the exception. It is a freemarker.core.InvalidReferenceException that extends java.lang.Exception and should be caught by Struts2. For all other exceptions thrown in Struts, the exceptions are caught and the global exception message is displayed. Seems like in this situation the action is executed first and after that the Freemarker exception is thrown, so maybe that's why it isn't caught by Struts. – Lucian Olosutean Mar 21 '16 at 13:08
  • Yes, you are right. You can try to print redirect with the exception handler. See http://freemarker.624813.n4.nabble.com/URL-redirect-in-ftl-td625576.html. – Aleksandr M Mar 21 '16 at 13:13
  • See how `HTML_DEBUG_HANDLER` prints - https://github.com/apache/incubator-freemarker/blob/2.3-gae/src/main/java/freemarker/template/TemplateExceptionHandler.java#L98. BTW good question. :) – Aleksandr M Mar 21 '16 at 13:18
  • Thanks! The problem is that the PrintWriter that comes in the exception handler is not empty, there is the action's result page loaded there and then everything you write it is appended to what it was written in the Writer before. I can't believe that there is no simple solution to this problem! – Lucian Olosutean Mar 21 '16 at 13:51
  • So what? Is redirect not working? Don't forget that this is template and you usually don't redirect away from the templates. Nor receive errors in them in production. – Aleksandr M Mar 21 '16 at 13:57
  • Thank you for your help, Aleksandr! – Lucian Olosutean Mar 22 '16 at 15:10

2 Answers2

0

Ok, so my solution for this problem was to print on the PrintWriter that comes in my CustomTemplateExceptionHandler a response similar with the standard HTML_DEBUG_HANDLER offered by Freemarker. Check out this link:

https://github.com/apache/incubator-freemarker/blob/2.3-gae/src/main/java/freemarker/template/TemplateExceptionHandler.java#L98

Here you can see how HTML_DEBUG_HANDLER is managed. I replaced printing the stacktrace with a general message. The Freemarker documentation advise you to use RETHROW_HANDLER and catch the exception later in your application after the call of Template.process(). See here:

http://freemarker.org/docs/app_faq.html#misc.faq.niceErrorPage

But because Struts2 is working with Freemarker backstage, and the Freemarker methods are executed after the action was executed, I couldn't figure it out how and where to catch the exception. I have managed to get the HttpServlet response and request in the method handleTemplateException() (see the question), but I could not forward to my exception.jsp because the response was already committed and so it was giving me an exception.

The final code looks like this:

Class CustomFreemarkerManager:

@Override
public void init(ServletContext servletContext) throws TemplateException {
    super.config = super.createConfiguration(servletContext);
    super.config.setTemplateExceptionHandler(new CustomTemplateExceptionHandler());
    super.contentType = "text/html";
    super.wrapper = super.createObjectWrapper(servletContext);
    if (LOG.isDebugEnabled()) {
        LOG.debug("Using object wrapper of class " + super.wrapper.getClass().getName(), new String[0]);
    }

    super.config.setObjectWrapper(super.wrapper);
    super.templatePath = servletContext.getInitParameter("TemplatePath");
    if (super.templatePath == null) {
        super.templatePath = servletContext.getInitParameter("templatePath");
    }

    super.configureTemplateLoader(super.createTemplateLoader(servletContext, super.templatePath));
    super.loadSettings(servletContext);
}

@Override
protected Configuration createConfiguration(ServletContext servletContext) throws TemplateException {
    Configuration configuration = new Configuration();
    configuration.setTemplateExceptionHandler(new CustomTemplateExceptionHandler());
    if (super.mruMaxStrongSize > 0) {
        configuration.setSetting("cache_storage", "strong:" + super.mruMaxStrongSize);
    }

    if (super.templateUpdateDelay != null) {
        configuration.setSetting("template_update_delay", super.templateUpdateDelay);
    }

    if (super.encoding != null) {
        configuration.setDefaultEncoding(super.encoding);
    }

    configuration.setLocalizedLookup(false);
    configuration.setWhitespaceStripping(true);
    return configuration;
}

Class CustomTemplateExceptionHandler:

public void handleTemplateException(TemplateException te, Environment env, Writer out) throws TemplateException {

    boolean externalPw = out instanceof PrintWriter;
    PrintWriter pw = externalPw ? (PrintWriter) out : new PrintWriter(out);
    try {
        pw.print("<!-- ERROR MESSAGE STARTS HERE -->"
                + "<!-- ]]> -->"
                + "</table></table></table>"
                + "<div align='left' style='"
                + "background-color:#FFFF7C; "
                + "display:block; "
                + "border-top:double; "
                + "padding:10px; "
                + "'>");
        pw.print("<b style='"
                + "color: red; "
                + "font-size:14px; "
                + "font-style:normal; "
                + "font-weight:bold; "
                + "'>"
                + "Oops! We have encountered a problem. Please try again!"
                + "</b>");
        pw.println("</div></html>");
        pw.flush();  // To commit the HTTP response
    } finally {
        if (!externalPw) pw.close();
    }

    throw te;
}

If anyone finds a better response to this please post your answer!

0

It should be easier. If you don't want the yellow debug template error you have to switch the TemplateExceptionHandler from HTML_DEBUG_HANDLER to RETHROW_HANDLER (as the freemarker message on the top suggest: FreeMarker template error DEBUG mode; use RETHROW in production!)

Now, I prefer to do it programmatically (as you did) because I want to choose the TemplateExceptionHandler depending on the environment (PRO, TEST, DEVEL), my solution is to put environment information in the context.

public class CustomFreemarkerManager extends FreemarkerManager {    


    public CustomFreemarkerManager(){
        super();
    }

    @Override
    public void init(ServletContext servletContext) throws TemplateException {

        //important!
        super.init(servletContext);

        //other stuff maybe you want to tune...

        //Getting environmentInfo object from the context, it's a personal solution 
        EnvironmentInfo environmentInfo = (EnvironmentInfo)servletContext.getAttribute(EnvironmentInfo.CONTEXT_NAME);

        if (environment.isPro()) {
            config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        }else{
            config.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);    
        }

    }
}

and tell struts to use your manager in struts.properties

struts.freemarker.manager.classname=com.jobisjob.northpole.web.core.CustomFreemarkerManager

Hope this helps.

fustaki
  • 1,574
  • 1
  • 13
  • 20