viernes, 20 de julio de 2012

Localized 404 errors

Have you ever needed custom 404 error pages for your Magnolia CMS website? Such a requirement is quite straightforward to implement if the site uses only one language. It gets more complicated when multiple languages are used, each with its own domain such as mysite.de and mysite.fr. In such a case host based virtual URI mapping is useful as explained in How to set up a custom 404 handler.

But what if the languages are not host based? What if you don't have .de or .fr domains? The language of the page can be built into the URL such as mysite.com/de/page.html or mysite.com/fr/page.html. In this post I show how to direct the request to a custom 404 error page using a custom filter.

Tip! I started by investigating if I could configure the exceptions in the web.xml file. I thought the exception-type tag might be the answer. Turns out it doesn't work within the Magnolia CMS filter chain. In case you had the same idea I will save you the trouble of going that route.

The solution is to write a custom filter to handle HTTP errors. The custom filter will extend the default AggregatorFilter. Replace the default class with your custom class in the CMS filter chain in Configuration > /server/filters/cms/aggregator.


The class code should look like this:
public class CustomAggregatorFilter extends AggregatorFilter {
 private static final Logger log = LoggerFactory.getLogger(CustomAggregatorFilter.class);

 @Override
    public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException{

        boolean success;
        try {
            success = collect();
        }
        catch (AccessDeniedException e) {
            // don't throw further, simply return error and break filter chain
            log.debug(e.getMessage(), e);
            if (!response.isCommitted()) {
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            }
            // stop the chain
            return;
        }
        catch (RepositoryException e) {
            log.error(e.getMessage(), e);
            throw new ServletException(e.getMessage(), e);
        }
        if (!success) {
            log.debug("Resource not found, redirecting request for [{}] to 404 URI", request.getRequestURI());

            if (!response.isCommitted()) {
             if(MgnlContext.getAggregationState().getLocale().getLanguage().equals("en")){
              RequestDispatchUtil.dispatch("permanent:/en.html", request, response);
             } else {
              RequestDispatchUtil.dispatch("permanent:/de.html", request, response);
             }
            }
            else {
                log.info("Unable to redirect to 404 page, response is already committed. URI was {}", request.getRequestURI());
            }
            // stop the chain
            return;
        }
        chain.doFilter(request, response);
    } 
}

Note that the filter only reacts to 404 errors. It checks the language from the aggregation state. This is the same language as used in the URL, such as mysite.com/de/page.html. Based on the language the filter makes a permanent redirect to the German error page that is part of the website. On the error page you can display whatever content you like, send parameters, log statements and so on.

The filter creates a permanent (301) link. This is the SEO friendly way. Search engines will index the pages while you can still keep track of the errors by setting them in the response the way you decide.

This is just a demonstration. It could be improved by adding the errors and URLs in the filter configuration, for example.

Want to learn more? Register now for Magnolia Conference 2012!