org.segrada.servlet.SegradaSimplePageCachingFilter.java Source code

Java tutorial

Introduction

Here is the source code for org.segrada.servlet.SegradaSimplePageCachingFilter.java

Source

package org.segrada.servlet;

import com.google.inject.Injector;
import com.google.inject.Singleton;
import net.sf.ehcache.constructs.web.PageInfo;
import net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter;
import org.apache.commons.codec.binary.Hex;
import org.segrada.session.Identity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Copyright 2015 Maximilian Kalus [segrada@auxnet.de]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Customized cache filter that filters out certain pages which should not be cached
 */
@Singleton
public class SegradaSimplePageCachingFilter extends SimplePageCachingFilter {
    private final static Logger logger = LoggerFactory.getLogger(SegradaSimplePageCachingFilter.class.getName());

    /**
     * excluded patterns
     */
    private static final Pattern excludePatterns = Pattern.compile("/(clear_cache|reindex|locale/)");

    /**
     * url parts that add a session key to the cache key in order to function properly
     */
    private static final Pattern addSessionToCacheKey = Pattern
            .compile("^/((node|source|file|relation)(/by_tag/[0-9\\-]+)?|relation_type|pictogram|tag|color)$");

    /**
     * pattern to filter out jsessionid-urls
     */
    private static final Pattern jSessionFilter = Pattern.compile(";jsessionid=[a-zA-Z0-9]+$");

    /**
     * reference to injector
     */
    private static Injector injector;

    /**
     * set the injector - called by Bootstrap
     */
    public static void setInjector(Injector injector) {
        SegradaSimplePageCachingFilter.injector = injector;
    }

    @Override
    protected void doFilter(HttpServletRequest servletRequest, HttpServletResponse servletResponse,
            FilterChain filterChain) throws Exception {
        // exclude?
        String url = servletRequest.getRequestURL().toString();

        // session set?
        Identity identity = injector.getInstance(Identity.class);
        boolean loggedIn = identity.getName() != null;

        // only get request and matched urls when logged in and not on POST methods
        if (!loggedIn || servletRequest.getMethod().equals("POST") || excludePatterns.matcher(url).find()) {
            if (logger.isDebugEnabled())
                logger.debug("Not caching url " + url);

            // build page info and return gzipped if needed
            PageInfo pageInfo = this.buildPage(servletRequest, servletResponse, filterChain);
            if (pageInfo.isOk() && !servletResponse.isCommitted()) {
                this.writeResponse(servletRequest, servletResponse, pageInfo);
            }
        } else {
            // included cached filter chain loaded
            super.doFilter(servletRequest, servletResponse, filterChain);
        }
    }

    /**
     * calculate key for page from httpRequest
     * @param httpRequest the request
     * @return cache key
     */
    protected String calculateKey(HttpServletRequest httpRequest) {
        HttpSession session = httpRequest.getSession();

        // get language from session
        Object lObject = session.getAttribute("language");
        String language = lObject == null ? null : (String) lObject;
        if (language == null)
            language = httpRequest.getLocale().getLanguage();

        // get url, context path stripped
        String urlPart = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length());

        // filter out jsessionid URL addition, just in case - should not happen, because it is not clean, but nevertheless
        urlPart = jSessionFilter.matcher(urlPart).replaceFirst(""); //TODO: might be faster to use lastIndexOf and substr to replace this instead of regex

        // query data map - later to be sorted into a key
        SortedMap<String, String> queryData = new TreeMap<>();

        // match with urls that require session key addition to cache in order to function properly?
        MatchResult matchResult = addSessionToCacheKey.matcher(urlPart).toMatchResult();
        if (((Matcher) matchResult).find()) {
            // this is the same as the session key
            String controller = matchResult.group().substring(1, 2).toUpperCase() + matchResult.group().substring(2)
                    + "Service";
            // get session data
            Object controllerData = session.getAttribute(controller);

            // create sorted map
            if (controllerData != null && controllerData instanceof Map) {
                @SuppressWarnings("unchecked")
                Map<String, Object> d = (Map<String, Object>) controllerData;
                for (Map.Entry<String, Object> entry : d.entrySet()) {
                    Object o = entry.getValue();
                    String value;
                    // concatenate string array in order to make caching work
                    if (o instanceof String[])
                        value = String.join(",", (String[]) o);
                    else // all other cases: convert to string
                        value = entry.getValue().toString();
                    queryData.put(entry.getKey(), value);
                }
            }
        }

        // get query data and add it to list (overwrite session, if needed)
        boolean clearTags = false; // flag to check whether parameter clearTags was sent in form data
        boolean tagsSetByForm = false; // flag to check whether there has been a field tags in form data
        for (Map.Entry<String, String[]> parameter : httpRequest.getParameterMap().entrySet()) {
            String key = parameter.getKey();
            if (key.equals("clearTags")) {
                clearTags = true;
                continue; // do not add to parameters
            }
            if (key.equals("tags"))
                tagsSetByForm = true; // tags were in form data
            queryData.put(key, String.join(",", parameter.getValue()));
        }
        // did we have field clearTags in form, but no tags were set? => delete tags saved in session, if needed
        if (clearTags && !tagsSetByForm)
            queryData.remove("tags"); // will be removed from session via controller

        // create query string as key
        StringBuilder queryString = new StringBuilder();
        for (Map.Entry<String, String> entry : queryData.entrySet()) {
            try {
                String encodedName = URLEncoder.encode(entry.getKey(), "UTF-8");
                Object value = entry.getValue();
                String encodedValue = value != null ? URLEncoder.encode(String.valueOf(value), "UTF-8") : "";
                // do not include page=1
                if (encodedName.equals("page") && encodedValue.equals("1"))
                    continue;
                if (queryString.length() > 0)
                    queryString.append('&');
                queryString.append(encodedName).append('=').append(encodedValue);
            } catch (Exception e) {
                logger.warn("Could not encode field " + entry.getKey() + " with value " + entry.getValue(), e);
            }
        }

        // get session id - make session dependent
        Identity identity = injector.getInstance(Identity.class);
        String id;
        // not logged in/no session
        if (identity == null || !identity.isAuthenticated() || identity.getId() == null)
            id = "NOTLOGGEDIN";
        // admin view? all admins get the same view
        else if (identity.hasRole("ADMIN"))
            id = "ADMIN";
        // single user cache - might have access to certain elements only
        else
            id = identity.getId();

        // create key
        return httpRequest.getMethod() + language + urlPart + id + encode(queryString);
    }

    /**
     * encode (md5) string builder
     * @param sb string builder to encode
     * @return encoded string
     */
    private String encode(StringBuilder sb) {
        String encoded = sb.toString();
        // do we actually need to convert to md5?
        if (!encoded.isEmpty())
            try {
                MessageDigest md = MessageDigest.getInstance("MD5");
                encoded = Hex.encodeHexString(md.digest(encoded.getBytes("UTF-8")));
            } catch (Exception e) {
                // do nothing - just use query string as it is
            }
        return encoded;
    }
}