/**
 * JOnAS
 * Copyright (C) 2010 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.audit.internal.webauditor.filters;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import javax.management.MBeanServer;
import javax.management.Notification;
import javax.management.modelmbean.ModelMBeanNotificationBroadcaster;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.ow2.jonas.audit.internal.webauditor.report.WebInvocationAuditReportFactory;
import org.ow2.util.auditreport.api.IAuditID;
import org.ow2.util.auditreport.api.ICurrentInvocationID;
import org.ow2.util.auditreport.impl.AuditIDImpl;
import org.ow2.util.auditreport.impl.CurrentInvocationID;
import org.ow2.util.auditreport.impl.WebInvocationAuditReport;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;

/**
 * The default filter for web container.
 * @author Mathieu ANCELIN
 */
public class HttpAuditFilter implements Filter {

    /**
     * Logger.
     */
    private static Log logger = LogFactory.getLog(HttpAuditFilter.class);
    public static final String HEADER_INVOCATION_ID = "Invocation-ID";

    /**
     * Config of the filter.
     */
    private FilterConfig filterConfig = null;

    /**
     * Number of the sequence.
     */
    private long seqNumber = 0;

    /**
     * Filter names for exclusion list.
     */
    private List<String> filters = new ArrayList<String>();

    /**
     * Factory for reports.
     */
    private WebInvocationAuditReportFactory webInvocationAuditReportFactory = null;

    /**
     * Notification sender.
     */
    private ModelMBeanNotificationBroadcaster broadcaster = null;

    /**
     * The constructor with build of the exclusion list.
     * @param jmxServer the JMX Server
     */
    public HttpAuditFilter(final MBeanServer jmxServer) {
        this.webInvocationAuditReportFactory = new WebInvocationAuditReportFactory(jmxServer);
        filters.add("graniteamf");
        filters.add(".png");
        filters.add(".jpeg");
        filters.add(".jpg");
        filters.add(".gif");
        filters.add(".css");
        filters.add(".bmp");
        filters.add(".ico");
    }

    /**
     * Filter uri with exclusion list.
     * @param uri uri to filter.
     * @return filter or not.
     */
    private boolean uriFilter(final String uri) {
        boolean ret = false;
        for (String elem : filters) {
            if (uri.contains(elem)) {
                ret = true;
            }
        }
        return ret;
    }

    /**
     * Do the before process.
     * @param request the request.
     * @param response the response.
     * @throws IOException exceptions for the before process.
     * @throws ServletException exceptions for the before process.
     * @return a report
     */
    private WebInvocationAuditReport doBeforeProcessing(final ServletRequest request, final ServletResponse response)
            throws IOException, ServletException {
        HttpServletRequest hreq = (HttpServletRequest) request;

        ICurrentInvocationID current = CurrentInvocationID.getInstance();
        String invocationId = hreq.getHeader(HEADER_INVOCATION_ID);

        if (invocationId != null) {

            // Creates an ID from the read parameter and install it in the ThreadLocal
            IAuditID id = new AuditIDImpl(invocationId);
            current.setAuditID(id);
        } else {
            // Generate a new ID for this call (no parent ID as this is the first call)
            current.init(null);
        }

        seqNumber++;
        return webInvocationAuditReportFactory.prepareAuditReport(seqNumber, System.currentTimeMillis(), System.nanoTime(),
                Thread.currentThread(), "web", hreq);
    }

    /**
     * Do the after process.
     * @param request the request.
     * @param response the response.
     * @param report the report to complete
     * @throws IOException exceptions for the after process.
     * @throws ServletException exceptions for the after process.
     */
    private void doAfterProcessing(final ServletRequest request, final HttpServletResponse response,
            final WebInvocationAuditReport report) throws IOException, ServletException {

        // reset
        CurrentInvocationID.getInstance().setAuditID(null);
        try {
            int status = -1;
            Method m = response.getClass().getMethod("getStatus");
            if (m != null) {
                Integer i = (Integer) m.invoke(response);
                if (i != null) {
                    status = i.intValue();
                }
            }
            if (report != null) {
                webInvocationAuditReportFactory.completeAuditReport(report, System.nanoTime(), response.getContentType());
                report.setStatus(status);
                broadcaster.sendNotification(new Notification(WebInvocationAuditReport.class.getName(), broadcaster, seqNumber,
                        report.toString()));
            }

        } catch (Exception e) {
            logger.error("Unable to make a report", e);
        }
    }

    /**
     * Do the filter process.
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     * @param chain The filter chain we are processing
     * @exception IOException if an input/output error occurs
     * @exception ServletException if a servlet error occurs
     */
    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
            throws IOException, ServletException {
        // do nothing
        if (uriFilter(((HttpServletRequest) request).getRequestURI())) {
            chain.doFilter(request, response);
            return;
        }

        HttpServletResponse httpServletResponse = (HttpServletResponse) response;

        WebInvocationAuditReport report = doBeforeProcessing(request, httpServletResponse);
        try {
            chain.doFilter(request, httpServletResponse);
        } finally {
            doAfterProcessing(request, httpServletResponse, report);
        }
    }

    /**
     * Return the filter configuration object for this filter.
     * @return the filter config.
     */
    public FilterConfig getFilterConfig() {
        return (this.filterConfig);
    }

    /**
     * Set the filter configuration object for this filter.
     * @param filterConfig The filter configuration object
     */
    public void setFilterConfig(final FilterConfig filterConfig) {
        this.filterConfig = filterConfig;
    }

    /**
     * Destroy method for this filter.
     */
    public void destroy() {
    }

    /**
     * Init method for this filter.
     * @param filterConfig the filter config to init.
     */
    public void init(final FilterConfig filterConfig) {
        this.filterConfig = filterConfig;
    }

    /**
     * @return string representation of this object
     */
    @Override
    public String toString() {
        if (filterConfig == null) {
            return ("SimpleFilter()");
        }
        StringBuffer sb = new StringBuffer("SimpleFilter(");
        sb.append(filterConfig);
        sb.append(")");
        return (sb.toString());
    }

    /**
     * Method that get stacktrace.
     * @param t the concerned throwable.
     * @return the current stacktrace in displayable format.
     */
    public static String getStackTrace(final Throwable t) {
        String stackTrace = null;
        try {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            t.printStackTrace(pw);
            pw.close();
            sw.close();
            stackTrace = sw.getBuffer().toString();
        } catch (Exception ex) {
            System.out.println(ex);
        }
        return stackTrace;
    }

    /**
     * Log method.
     * @param msg the log message.
     */
    public void log(final String msg) {
        filterConfig.getServletContext().log(msg);
    }

    /**
     * Sets the given broadcaster in order to send notification.
     * @param broadcaster the given object
     */
    public void setBroadcaster(final ModelMBeanNotificationBroadcaster broadcaster) {
        this.broadcaster = broadcaster;
    }

}
