//
//  ========================================================================
//  Copyright (c) 1995-2012 Sabre Holdings.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//


package org.eclipse.jetty.ant;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.jetty.ant.types.Connector;
import org.eclipse.jetty.ant.types.ContextHandlers;
import org.eclipse.jetty.ant.utils.ServerProxy;
import org.eclipse.jetty.ant.utils.TaskLog;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.ShutdownMonitor;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.util.Scanner;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.xml.XmlConfiguration;
import org.xml.sax.SAXException;



/**
 * A proxy class for interaction with Jetty server object. Used to have some
 * level of abstraction over standard Jetty classes.
 */
public class ServerProxyImpl implements ServerProxy
{

    /** Proxied Jetty server object. */
    private Server server;
    
    /** Temporary files directory. */
    private File tempDirectory;
    
    /** Collection of context handlers (web application contexts). */
    private ContextHandlerCollection contexts;

    /** Location of jetty.xml file. */
    private File jettyXml;

    /** List of connectors. */
    private List<Connector> connectors;

    /** Request logger. */
    private RequestLog requestLog;

    /** User realms. */
    private List<LoginService> loginServices;

    /** List of added web applications. */
    private List<AntWebAppContext> webApplications = new ArrayList<AntWebAppContext>();

    /** other contexts to deploy */
    private ContextHandlers contextHandlers;

    /** scan interval for changed files */
    private int scanIntervalSecs;

    /** port to listen for stop command */
    private int stopPort;

    /** security key for stop command */
    private String stopKey;

    /** wait for all jetty threads to exit or continue */
    private boolean daemon;


    private boolean configured = false;


    
    /**
     * WebAppScannerListener
     *
     * Handle notifications that files we are interested in have changed
     * during execution.
     * 
     */
    public static class WebAppScannerListener implements Scanner.BulkListener
    {     
        AntWebAppContext awc;

        public WebAppScannerListener (AntWebAppContext awc)
        {
            this.awc = awc;
        }

        public void filesChanged(List<String> changedFileNames)
        {
            boolean isScanned = false;
            try
            {
                Iterator<String> itor = changedFileNames.iterator();
                while (!isScanned && itor.hasNext())
                {
                    isScanned = awc.isScanned(Resource.newResource(itor.next()).getFile());
                }
                if (isScanned)
                {
                    awc.stop();
                    awc.start();
                }
            }
            catch (Exception e)
            {
                TaskLog.log(e.getMessage());
            }
        }

    }


    /**
     * Default constructor. Creates a new Jetty server with a standard connector
     * listening on a given port.
     */
    public ServerProxyImpl ()
    {
        server = new Server();
        server.setStopAtShutdown(true);
    }

   
    public void addWebApplication(AntWebAppContext webApp)
    {
       webApplications.add(webApp);
    }

    public int getStopPort()
    {
        return stopPort;
    }

    public void setStopPort(int stopPort)
    {
        this.stopPort = stopPort;
    }

    public String getStopKey()
    {
        return stopKey;
    }

    public void setStopKey(String stopKey)
    {
        this.stopKey = stopKey;
    }

    public File getJettyXml()
    {
        return jettyXml;
    }

    public void setJettyXml(File jettyXml)
    {
        this.jettyXml = jettyXml;
    }

    public List<Connector> getConnectors()
    {
        return connectors;
    }

    public void setConnectors(List<Connector> connectors)
    {
        this.connectors = connectors;
    }

    public RequestLog getRequestLog()
    {
        return requestLog;
    }

    public void setRequestLog(RequestLog requestLog)
    {
        this.requestLog = requestLog;
    }

    public List<LoginService> getLoginServices()
    {
        return loginServices;
    }

    public void setLoginServices(List<LoginService> loginServices)
    {
        this.loginServices = loginServices;
    }

    public List<AntWebAppContext> getWebApplications()
    {
        return webApplications;
    }

    public void setWebApplications(List<AntWebAppContext> webApplications)
    {
        this.webApplications = webApplications;
    }

    
    public File getTempDirectory()
    {
        return tempDirectory;
    }


    public void setTempDirectory(File tempDirectory)
    {
        this.tempDirectory = tempDirectory;
    }


    /**
     * @see org.eclipse.jetty.ant.utils.ServerProxy#start()
     */
    public void start()
    {
        try
        {
            configure();
            
            configureWebApps();
            
            server.start();
         
            System.setProperty("jetty.ant.server.port","" + ((ServerConnector)server.getConnectors()[0]).getLocalPort());
            
            String host = ((ServerConnector)server.getConnectors()[0]).getHost();
            
            if (host == null)
            {
                System.setProperty("jetty.ant.server.host", "localhost");
            }
            else
            {
                System.setProperty("jetty.ant.server.host", host);
            }
            
            startScanners();
            
            TaskLog.log("Jetty AntTask Started");

            if (!daemon)
                server.join();
        }
        catch (InterruptedException e)
        {
            new RuntimeException(e);
        }
        catch (Exception e)
        {
            new RuntimeException(e);
        }
    }



  
    /**
     * @see org.eclipse.jetty.ant.utils.ServerProxy#getProxiedObject()
     */
    public Object getProxiedObject()
    {
        return server;
    }


    /**
     * @return the daemon
     */
    public boolean isDaemon()
    {
        return daemon;
    }


    /**
     * @param daemon the daemon to set
     */
    public void setDaemon(boolean daemon)
    {       
        this.daemon = daemon;
    }


    /**
     * @return the contextHandlers
     */
    public ContextHandlers getContextHandlers()
    {
        return contextHandlers;
    }


    /**
     * @param contextHandlers the contextHandlers to set
     */
    public void setContextHandlers (ContextHandlers contextHandlers)
    {
        this.contextHandlers = contextHandlers;
    }


    public int getScanIntervalSecs()
    {
        return scanIntervalSecs;
    }


    public void setScanIntervalSecs(int scanIntervalSecs)
    {
        this.scanIntervalSecs = scanIntervalSecs;
    }
    

    /**
     * Configures Jetty server before adding any web applications to it.
     */
    private void configure()
    {
        if (configured)
            return;
        
        configured = true;
        
        ShutdownMonitor monitor = ShutdownMonitor.getInstance();
        monitor.setPort(stopPort);
        monitor.setKey(stopKey);
        monitor.setExitVm(false);
        
        if (tempDirectory != null && !tempDirectory.exists())
            tempDirectory.mkdirs();
        
        // Applies external configuration via jetty.xml
        applyJettyXml();

        // Configures connectors for this server instance.
        if (connectors != null)
        {
            for (Connector c:connectors)
            {
                ServerConnector jc = new ServerConnector(server);

                jc.setPort(c.getPort());
                jc.setIdleTimeout(c.getMaxIdleTime());

                server.addConnector(jc);
            }
        }

        // Configures login services
        if (loginServices != null)
        {
            for (LoginService ls:loginServices)
            {
                server.addBean(ls);
            }
        }

        // Does not cache resources, to prevent Windows from locking files
        Resource.setDefaultUseCaches(false);

        // Set default server handlers
        configureHandlers();
    }
    
    
    /**
     * 
     */
    private void configureHandlers()
    {
        RequestLogHandler requestLogHandler = new RequestLogHandler();
        if (requestLog != null)
            requestLogHandler.setRequestLog(requestLog);

        contexts = (ContextHandlerCollection) server
                .getChildHandlerByClass(ContextHandlerCollection.class);
        if (contexts == null)
        {
            contexts = new ContextHandlerCollection();
            HandlerCollection handlers = (HandlerCollection) server
                    .getChildHandlerByClass(HandlerCollection.class);
            if (handlers == null)
            {
                handlers = new HandlerCollection();
                server.setHandler(handlers);
                handlers.setHandlers(new Handler[] { contexts, new DefaultHandler(),
                        requestLogHandler });
            }
            else
            {
                handlers.addHandler(contexts);
            }
        }
        
        //if there are any extra contexts to deploy
        if (contextHandlers != null && contextHandlers.getContextHandlers() != null)
        {
            for (ContextHandler c:contextHandlers.getContextHandlers())
                contexts.addHandler(c);
        }
    }



    
    /**
     * Applies jetty.xml configuration to the Jetty server instance.
     */
    private void applyJettyXml()
    {
        if (jettyXml != null && jettyXml.exists())
        {
            TaskLog.log("Configuring jetty from xml configuration file = "
                    + jettyXml.getAbsolutePath());
            XmlConfiguration configuration;
            try
            {
                configuration = new XmlConfiguration(Resource.toURL(jettyXml));
                configuration.configure(server);
            }
            catch (MalformedURLException e)
            {
                throw new RuntimeException(e);
            }
            catch (SAXException e)
            {
                throw new RuntimeException(e);
            }
            catch (IOException e)
            {
                throw new RuntimeException(e);
            }
            catch (Exception e)
            {
                throw new RuntimeException(e);
            }
        }
    }

    
    /**
     * Starts web applications' scanners.
     */
    private void startScanners() throws Exception
    {
        for (AntWebAppContext awc:webApplications)
        {
            if (scanIntervalSecs <= 0)
                return;

            List<File> scanList = awc.getScanFiles();
 
            TaskLog.log("Web application '" + awc + "': starting scanner at interval of "
                    + scanIntervalSecs + " seconds.");
            Scanner.Listener changeListener = new WebAppScannerListener(awc);
            Scanner scanner = new Scanner();
            scanner.setScanInterval(scanIntervalSecs);
            scanner.addListener(changeListener);
            scanner.setScanDirs(scanList);
            scanner.setReportExistingFilesOnStartup(false);
            scanner.start();
        }  
    }
    
    
    /**
     * 
     */
    private void configureWebApps()
    {
        for (AntWebAppContext awc:webApplications)
        {
            awc.setAttribute(AntWebAppContext.BASETEMPDIR, tempDirectory);
            if (contexts != null)
                contexts.addHandler(awc);
        }
    }
    
}
