/**
 * JOnAS
 * Copyright (C) 2010-2012 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.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.GenericAuditReport;
import org.ow2.util.auditreport.impl.InvocationAuditReport;
import org.ow2.util.auditreport.impl.JNDIAuditReport;
import org.ow2.util.auditreport.impl.JaxwsAuditReport;
import org.ow2.util.auditreport.impl.WebInvocationAuditReport;
import org.ow2.util.auditreport.impl.event.Event;
import org.ow2.util.event.api.IEvent;
import org.ow2.util.event.api.IEventService;
import org.ow2.util.event.impl.EventDispatcher;
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;

    /**
     * eventService to send event report
     */
    private IEventService eventService = 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(jmxServer, null);
    }
    
    /**
     * The constructor with build of the exclusion list.
     * 
     * @param jmxServer
     * the JMX Server
     * @param eventService
     * the EventService
     */
    public HttpAuditFilter(MBeanServer jmxServer, IEventService eventService) {
        this.eventService = eventService;
        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(eventService.getDispatcher("WEB")==null) {
                EventDispatcher d = new EventDispatcher();
                d.setNbWorkers(2);
                d.start();
                eventService.registerDispatcher("WEB", d);
            }
            if (report != null) {
                webInvocationAuditReportFactory.completeAuditReport(report, System.nanoTime(), response.getContentType());
                report.setStatus(status);
                Event e = new Event(report);
                sendEvent(e);
            }

        } 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);
    }

    /**
     * To send event to an approriate dispatcher following different type of Event
     * JNDI,WEB,JAXWS,EJB..
     * 
     * @param e
     * The event to send
     */
    public void sendEvent(IEvent e) {
        GenericAuditReport report = ((Event) e).getReport();
        if (report instanceof WebInvocationAuditReport) {
            eventService.getDispatcher("WEB").dispatch(e);
        } else if (report instanceof InvocationAuditReport) {
            eventService.getDispatcher("EJB").dispatch(e);
        } else if (report instanceof JNDIAuditReport) {
            eventService.getDispatcher("JNDI").dispatch(e);
        } else if (report instanceof JaxwsAuditReport) {
            eventService.getDispatcher("JAXWS").dispatch(e);
        } else {
            logger.error("Not be able to dispatch event correctly on dispatchers.The event is unknown therefore don't dispatch");
        }

    }

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

}
