/*-
 *  Copyright 2015 Crimson Hexagon
 *
 *  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.
 */

package com.crimsonhexagon.rsm;

import org.apache.catalina.Context;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;

import java.io.IOException;
import java.util.regex.Pattern;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;


/**
 * {@link Valve} to ensure session is persisted to redis after the request has completed
 *
 * @author Steve Ungerer
 */
public class RedisSessionRequestValve extends ValveBase {
    private static final Log log = LogFactory.getLog(RedisSessionRequestValve.class);
    private final RedisSessionManager manager;
    private final Pattern ignorePattern;

    /**
     * Default pattern for request URIs to ignore:
     * Ignore ico|png|gif|jpg|jpeg|swf|css|js that appear in the requestURI (query strings not presented for matching)
     */
    public static final String DEFAULT_IGNORE_PATTERN = ".*\\.(ico|png|gif|jpg|jpeg|swf|css|js)$";

    private static final String POST_METHOD = "post";
    // note key to store the query string
    protected static final String REQUEST_QUERY = "com.crimsonhexagon.rsm.QUERY_STRING";

    public RedisSessionRequestValve(RedisSessionManager manager, String ignorePattern) {
        this.manager = manager;
        if (ignorePattern != null && ignorePattern.trim().length() > 0) {
            this.ignorePattern = Pattern.compile(ignorePattern, Pattern.CASE_INSENSITIVE);
        } else {
            this.ignorePattern = null;
        }
    }

    @Override
    public void invoke(Request request, Response response) throws IOException, ServletException {
        Context context = request.getContext();
        if (context == null) {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, sm.getString("standardHost.noContext"));
            return;
        }
        Thread.currentThread().setContextClassLoader(context.getLoader().getClassLoader());

        boolean processed = false;
        try {
            if (ignorePattern == null || !ignorePattern.matcher(request.getRequestURI()).matches()) {
                processed = true;
                if (log.isTraceEnabled()) {
                    log.trace("Will save to redis after request for [" + getQueryString(request) + "]");
                }
            } else {
                if (log.isTraceEnabled()) {
                    log.trace("Ignoring [" + getQueryString(request) + "]");
                }
            }
            getNext().invoke(request, response);
        } finally {
            manager.afterRequest(processed);
        }
    }

    @Override
    public boolean isAsyncSupported() {
        return true;
    }

    /**
     * Get the full query string of the request; used only for logging
     * 
     * @param request
     * @return
     */
    private String getQueryString(final Request request) {
        final StringBuilder sb = new StringBuilder();
        sb.append(request.getMethod()).append(' ').append(request.getRequestURI());
        if (!isPostMethod(request) && request.getQueryString() != null) {
            sb.append('?').append(request.getQueryString());
        }
        final String result = sb.toString();
        request.setNote(REQUEST_QUERY, result);
        return result;
    }

    protected boolean isPostMethod(final Request request) {
        return POST_METHOD.equalsIgnoreCase(request.getMethod());
    }

    RedisSessionManager getManager() {
        return manager;
    }

}