/*
 * JBoss, Home of Professional Open Source Copyright 2010, Red Hat Inc., and individual contributors as indicated by the
 *
 * @authors tag. See the copyright.txt in the distribution for a full listing of individual contributors.
 *
 * This 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 (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.as.web;

import static org.jboss.as.web.WebMessages.MESSAGES;

import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;

import javax.management.MBeanServer;
import javax.security.jacc.PolicyContext;

import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.AprLifecycleListener;
import org.apache.catalina.core.JasperListener;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardServer;
import org.apache.catalina.core.StandardService;
import org.apache.tomcat.util.modeler.Registry;
import org.jboss.as.controller.services.path.PathManager;
import org.jboss.as.web.security.HttpServletRequestPolicyContextHandler;
import org.jboss.msc.service.Service;
import org.jboss.msc.service.StartContext;
import org.jboss.msc.service.StartException;
import org.jboss.msc.service.StopContext;
import org.jboss.msc.value.InjectedValue;
import org.jboss.security.SecurityConstants;

/**
 * Service configuring and starting the web container.
 *
 * @author Emanuel Muckenhuber
 * @author Jean-Frederic Clere
 */
public class WebServerService implements WebServer, Service<WebServer> {

    private static final String JBOSS_WEB = "jboss.web";

    private final String defaultHost;
    private final boolean useNative;
    private final String instanceId;
    private final String tempPathName;

    private Engine engine;
    private StandardServer server;
    private StandardService service;
    private Map<String, AuthenticatorValve> authenvalves = new Hashtable<String, AuthenticatorValve>();

    private final InjectedValue<MBeanServer> mbeanServer = new InjectedValue<MBeanServer>();
    private final InjectedValue<PathManager> pathManagerInjector = new InjectedValue<PathManager>();

    public WebServerService(final String defaultHost, final boolean useNative, final String instanceId,
            final String tempPathName) {
        this.defaultHost = defaultHost;
        this.useNative = useNative;
        this.instanceId = instanceId;
        this.tempPathName = tempPathName;
    }

    /** {@inheritDoc} */
    public synchronized void start(StartContext context) throws StartException {
        if (org.apache.tomcat.util.Constants.ENABLE_MODELER) {
            // Set the MBeanServer
            final MBeanServer mbeanServer = this.mbeanServer.getOptionalValue();
            if (mbeanServer != null) {
                Registry.getRegistry(null, null).setMBeanServer(mbeanServer);
            }
        }

        System.setProperty("catalina.home", pathManagerInjector.getValue().getPathEntry(tempPathName).resolvePath());
        final StandardServer server = new StandardServer();

        final StandardService service = new StandardService();
        service.setName(JBOSS_WEB);
        service.setServer(server);
        server.addService(service);

        final StandardEngine engine = new StandardEngine();
        engine.setName(JBOSS_WEB);
        engine.setService(service);
        engine.setDefaultHost(defaultHost);
        engine.setBaseDir(pathManagerInjector.getValue().getPathEntry(tempPathName).resolvePath());
        if (instanceId != null) {
            engine.setJvmRoute(instanceId);
        }

        service.setContainer(engine);

        if (useNative) {
            final AprLifecycleListener apr = new AprLifecycleListener();
            apr.setSSLEngine("on");
            server.addLifecycleListener(apr);
        }
        server.addLifecycleListener(new JasperListener());

        try {
            // Register the active request PolicyContextHandler
            HttpServletRequestPolicyContextHandler handler = new HttpServletRequestPolicyContextHandler();
            PolicyContext.registerHandler(SecurityConstants.WEB_REQUEST_KEY, handler, true);
            // Start the server
            server.init();
            server.start();
        } catch (Exception e) {
            throw new StartException(MESSAGES.errorStartingWeb(), e);
        }
        this.server = server;
        this.service = service;
        this.engine = engine;
    }

    /** {@inheritDoc} */
    @Override
    public synchronized void stop(StopContext context) {
        try {
            server.stop();
        } catch (Exception e) {
        }
        try {
            // Remove PolicyContextHandler
            Set handlerKeys = PolicyContext.getHandlerKeys();
            handlerKeys.remove(SecurityConstants.WEB_REQUEST_KEY);
        } catch (Exception e) {
        }
        engine = null;
        service = null;
        server = null;
    }

    /** {@inheritDoc} */
    public synchronized WebServer getValue() throws IllegalStateException {
        return this;
    }

    /** {@inheritDoc} */
    public synchronized void addConnector(Connector connector) {
        final StandardService service = this.service;
        service.addConnector(connector);
    }

    /** {@inheritDoc} */
    public synchronized void removeConnector(Connector connector) {
        final StandardService service = this.service;
        service.removeConnector(connector);
    }

    /** {@inheritDoc} */
    public synchronized void addHost(Host host) {
        final Engine engine = this.engine;
        engine.addChild(host);
    }

    /** {@inheritDoc} */
    public synchronized void removeHost(Host host) {
        final Engine engine = this.engine;
        engine.removeChild(host);
    }

    InjectedValue<MBeanServer> getMbeanServer() {
        return mbeanServer;
    }

    InjectedValue<PathManager> getPathManagerInjector() {
        return pathManagerInjector;
    }

    public StandardServer getServer() {
        return server;
    }

    public StandardService getService() {
        return service;
    }

    @Override
    public void addValve(Valve valve) {
        final Engine engine = this.engine;
        ((StandardEngine) engine).addValve(valve);
    }

    @Override
    public synchronized void addValve(String name, Class classz, Hashtable<String, String> properties) {
        AuthenticatorValve authvalve = new AuthenticatorValve();
        authvalve.classz = classz;
        authvalve.properties = properties;
        this.authenvalves.put(name, authvalve);
    }

    @Override
    public void removeValve(Valve valve) {
        final Engine engine = this.engine;
        ((StandardEngine) engine).removeValve(valve);
    }

    public Map<String, AuthenticatorValve> getAuthenValves() {
        return this.authenvalves;
    }
}
