/**
 * 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.report;

import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.servlet.http.HttpServletRequest;

import org.ow2.jonas.lib.security.context.SecurityContext;
import org.ow2.jonas.lib.security.context.SecurityCurrent;
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;

/**
 * This class build report based on invocations events.
 * @author Mathieu ANCELIN
 */
public class WebInvocationAuditReportFactory {

    /**
     * The value for call level at start.
     */
    private static final int START_LEVEL = -1;

    /**
     * Logger.
     */
    private static Log logger = LogFactory.getLog(WebInvocationAuditReportFactory.class);

    /**
     * The creating trigger.
     */
    private boolean creatingReports = true;

    /**
     * JMX server.
     */
    private MBeanServer jmxServer = null;

    /**
     * The constructor of the factory.
     * @param jmxServer the JMX Server used to perform JMX requests
     */
    public WebInvocationAuditReportFactory(final MBeanServer jmxServer) {
        this.jmxServer = jmxServer;
    }

    /**
     * This method create a minimal report with start informations and put it in
     * a map, waiting for ending informations.
     * @param invocationNumber the invocation number.
     * @param timestamp the start timestamp.
     * @param start the time (in ns) the invocation starts.
     * @param current the current thread.
     * @param role the role used for the invocation.
     * @param req the http request.
     * @return a report
     */
    public WebInvocationAuditReport prepareAuditReport(final long invocationNumber, final long timestamp, final long start, final Thread current,
            final String role, final HttpServletRequest req) {
        if (!this.creatingReports) {
            return null;
        }
        int freeMemoryBefore = (int) (Runtime.getRuntime().freeMemory());
        int totalMemoryBefore = (int) (Runtime.getRuntime().totalMemory());
        try {
            String prefix = "/webcontainer/root";
            String post = "";
            if (req.getRequestURI().equals("/") && req.getContextPath().equals("")) {
                post += "root";
            }
            WebInvocationAuditReport reporttmp = new WebInvocationAuditReport(req);

            // Sets the ID in the report
            reporttmp.setKeyID(CurrentInvocationID.getInstance().getAuditID().getID());

            reporttmp.setFreeMemoryBefore(freeMemoryBefore);
            int paramSize = req.getParameterMap().values().size();
            Object[] params = new String[paramSize];
            int i = 0;
            Enumeration<String> e = req.getParameterNames();
            while (e.hasMoreElements()) {
                String name = e.nextElement();
                String value = req.getParameter(name);
                params[i] = name + ":" + value;
                i++;
            }
            reporttmp.setMethodParameters(params);
            reporttmp.setMethodStackTrace(current.getStackTrace());
            reporttmp.setRequestStart(start);
            reporttmp.setRequestTimeStamp(timestamp);
            SecurityContext securityContext = SecurityCurrent.getCurrent().getSecurityContext();
            String roles = "";
            String username = "";
            // Not yet authenticated
            if (securityContext == null) {
                securityContext = new SecurityContext();
            }
            String[] rolesArray = securityContext.getRoles();
            if (rolesArray != null) {
                roles = Arrays.asList(rolesArray).toString();
            }
            username = securityContext.getPrincipalName();
            reporttmp.setRoles(roles);
            reporttmp.setUser(username);
            reporttmp.setTarget(prefix + req.getRequestURI() + post);
            String packagePath = getPackagePath(req.getContextPath());
            if (packagePath.charAt(0) == '/') {
                reporttmp.setTarget(reporttmp.getTarget().replace("/webcontainer/", "/webcontainer" + packagePath));
            } else {
                reporttmp.setTarget(reporttmp.getTarget().replace("/webcontainer/", "/webcontainer/" + packagePath));
            }
            reporttmp.setTotalMemoryBefore(totalMemoryBefore);

            return reporttmp;
        } catch (Exception ex) {
            logger.error("Unable to  prepare the audit report", ex);
            return null;
        }
    }

    /**
     * Get the package name of a servlet through JMX.
     * @param servletMapping the mapped servlet path.
     * @return the package name.
     */
    public String getPackagePath(final String servletMapping) {
        String[] warPath = {};
        String war = "";
        String[] earPath = {};
        String ear = "";
        Set<ObjectInstance> objs = new HashSet<ObjectInstance>();
        try {
            objs = jmxServer.queryMBeans(new ObjectName(":j2eeType=WebModule,name=//localhost" + servletMapping
                    + ",*"), null);
        } catch (MalformedObjectNameException e) {
            logger.error("Unable to get package from servlet mapping " + servletMapping, e);
        } catch (NullPointerException e) {
            logger.error("Unable to get package from servlet mapping " + servletMapping, e);
        }
        for (ObjectInstance obj : objs) {
            try {
                if (jmxServer.getAttribute(obj.getObjectName(), "warURL") != null) {
                    warPath = ((java.net.URL) jmxServer.getAttribute(obj.getObjectName(), "warURL")).toString().split("/");
                    war = warPath[warPath.length - 1];
                } else {
                    war = "";
                }
                if (jmxServer.getAttribute(obj.getObjectName(), "earURL") != null) {
                    earPath = ((java.net.URL) jmxServer.getAttribute(obj.getObjectName(), "earURL")).toString().split("/");
                    ear = earPath[earPath.length - 1];
                    ear = ear.substring(0, ear.lastIndexOf("_")) + ".ear";
                } else {
                    ear = "";
                }
            } catch (Exception e) {
                logger.error("Unable to get package from servlet mapping " + servletMapping, e);
            }
        }
        return ear + "/" + war + "/";
    }

    /**
     * Return a new report well endded.
     * @param report the report to complete
     * @param stop the time the event occured.
     * @param returnedObject the returned object.
     */
    public void completeAuditReport(final WebInvocationAuditReport report, final long stop, final Object returnedObject) {
        if (!this.creatingReports) {
            return;
        }
        long totalGarbageCollections = 0;
        long garbageCollectionTime = 0;
        for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
            long count = gc.getCollectionCount();
            if (count >= 0) {
                totalGarbageCollections += count;
            }
            long time = gc.getCollectionTime();
            if (time >= 0) {
                garbageCollectionTime += time;
            }
        }
        int freeMemoryAfter = (int) (Runtime.getRuntime().freeMemory());
        int totalMemoryAfter = (int) (Runtime.getRuntime().totalMemory());
        report.setFreeMemoryAfter(freeMemoryAfter);
        report.setTotalMemoryAfter(totalMemoryAfter);
        report.setMethodReturn(returnedObject);
        report.setRequestStop(stop);
        report.setSweepMarkTime(totalGarbageCollections);
        report.setScavengeTime(garbageCollectionTime);
    }

    /**
     * Setter on creatingreports.
     * @param creatingReportsParam the new value.
     */
    public void setCreatingReports(final boolean creatingReportsParam) {
        this.creatingReports = creatingReportsParam;
    }
}
