/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.tomcat.internal;

import java.io.*;
import java.net.*;

import org.apache.catalina.*;
import org.apache.catalina.core.*;
import org.apache.catalina.logger.*;
import org.apache.catalina.realm.*;
import org.apache.catalina.startup.*;
import org.apache.coyote.tomcat4.*;
import org.eclipse.core.runtime.*;
import org.eclipse.help.internal.appserver.*;

/**
 * Single engine, single host, single connector Tomcat Application Server.
 */
public class TomcatAppServer implements IWebappServer {
	private String hostAddress;
	private int port;
	// false until an attempt to start Tomcat
	private boolean isStarted = false;
	// true after started without problems
	private boolean running = false;

	private Embedded embedded = null;
	private Engine engine = null;
	private Host host = null;

	/**
	 * Constructs this class, but does not instantiates or start Tomcat classes
	 * until webapp are added.
	 */
	public TomcatAppServer() {
	}

	/**
	 * @see org.eclipse.help.internal.appserver.IWebappServer#start(int,
	 *      java.lang.String)
	 */
	public void start(int port, String hostAddress) throws CoreException {
		this.hostAddress = hostAddress;
		this.port = port;

		if (isStarted) {
			return;
		}
		isStarted = true;
		try {
			FileLogger logger = new FileLogger();
			logger.setDirectory(TomcatPlugin.getDefault().getStateLocation()
					.toOSString());
			embedded = new Embedded(logger, new MemoryRealm());
			embedded.setDebug(0);
			embedded.setLogger(logger);
			URL installURL = TomcatPlugin.getDefault().getBundle()
					.getEntry("/"); //$NON-NLS-1$
			URL resolvedURL = Platform.resolve(installURL);
			String home = Platform.asLocalURL(resolvedURL).getFile();
			System.setProperty("catalina.home", home); //$NON-NLS-1$
			String base = home;
			System.setProperty("catalina.base", base); //$NON-NLS-1$

			// start now, and then add all the contexts..
			embedded.start();

			// Create a very basic container hierarchy
			engine = embedded.createEngine();

			host = embedded.createHost("localhost", home + "/webapps"); //$NON-NLS-1$ //$NON-NLS-2$

			// all request go to our only host
			engine.setDefaultHost(host.getName());

			if (host instanceof StandardHost) {
				((StandardHost) host)
						.setErrorReportValveClass("org.eclipse.tomcat.internal.EclipseErrorReportValve"); //$NON-NLS-1$
			}
			engine.addChild(host);

			// Install the assembled container hierarchy
			PrintStream sysOut = System.out;
			// reassign standard output to prevent Tomcat from writing
			// its version message there.
			System.setOut(new PrintStream(new ByteArrayOutputStream()));
			try {
				embedded.addEngine(engine);
			} finally {
				System.setOut(sysOut);
			}

			// Root context
			Context root = embedded.createContext("", home + "/webapps/ROOT"); //$NON-NLS-1$ //$NON-NLS-2$
			// this line should be replaced once tomcat provides support
			// for setting the working directory
			if (root instanceof StandardContext) {
				((StandardContext) root)
						.setWorkDir(getWorkingDirectory("ROOT")); //$NON-NLS-1$
			}
			root.setLoader(embedded.createLoader(this.getClass()
					.getClassLoader()));
			host.addChild(root);

			InetAddress iAddress = null;
			if (this.hostAddress != null) {
				try {
					iAddress = InetAddress.getByName(this.hostAddress);
				} catch (UnknownHostException uhe) {
					// will default to all interfaces
				}
			}
			if (this.port == 0) {
				this.port = SocketUtil.findUnusedLocalPort(iAddress); //$NON-NLS-1$
				if (this.port == -1) {
					throw new CoreException(
							new Status(
									IStatus.ERROR,
									TomcatPlugin.PLUGIN_ID,
									IStatus.OK,
									TomcatResources
											.getString("TomcatAppServer.start.CannotObtainPort"), //$NON-NLS-1$
									null));
				}
			}

			// Create Connector
			Connector connector = embedded.createConnector(null, this.port,
					false);
			// Override defaults on CoyoteConnector
			if (connector instanceof CoyoteConnector) {
				CoyoteConnector connectorImpl = (CoyoteConnector) connector;
				if (iAddress != null) {
					// bug in Embedded that incorrectly sets host on connector.
					// pass null when creating connector, and set host here if
					// it is specified
					connectorImpl.setAddress(iAddress.getHostAddress());
				}
				Preferences pref = TomcatPlugin.getDefault()
						.getPluginPreferences();
				int acceptCount = pref.getInt(TomcatPlugin.PREF_ACCEPT_COUNT);
				if (acceptCount > 0) {
					connectorImpl.setAcceptCount(acceptCount);
				}
				int maxProcessors = pref
						.getInt(TomcatPlugin.PREF_MAX_PROCESSORS);
				if (maxProcessors > 0) {
					connectorImpl.setMaxProcessors(maxProcessors);
				}
				int minProcessors = pref
						.getInt(TomcatPlugin.PREF_MIN_PROCESSORS);
				if (minProcessors > 0) {
					connectorImpl.setMinProcessors(minProcessors);
				}
				//connectorImpl.setDebug(0);
				// If there is problem in embedded.addConnector()
				// there is no exception, so add a listener
				connectorImpl.addLifecycleListener(new LifecycleListener() {
					public void lifecycleEvent(LifecycleEvent event) {
						if ("start".equals(event.getType())) //$NON-NLS-1$
							running = true;
					}
				});
			}

			// add Connector to Tomcat
			PrintStream sysErr = System.err;
			// reassign standard error to prevent Coyote from writing
			// its version message there.
			System.setErr(new PrintStream(new ByteArrayOutputStream()));
			try {
				embedded.addConnector(connector);
			} finally {
				System.setErr(sysErr);
			}

			// if null passed for hostAddress, use local host
			if (this.hostAddress == null) {
				this.hostAddress = "127.0.0.1"; //$NON-NLS-1$
			}

			//running = true;

		} catch (Exception exc) {
			TomcatPlugin.logError(TomcatResources
					.getString("TomcatAppServer.start"), //$NON-NLS-1$
					exc);
			if (exc instanceof CoreException) {
				throw (CoreException) exc;
			} else {
				throw new CoreException(new Status(IStatus.ERROR,
						TomcatPlugin.PLUGIN_ID, IStatus.OK, TomcatResources
								.getString("TomcatAppServer.start"), //$NON-NLS-1$
						exc));
			}
		}
		if (!running) {
			throw new CoreException(new Status(IStatus.ERROR,
					TomcatPlugin.PLUGIN_ID, IStatus.OK, TomcatResources
							.getString("TomcatAppServer.start"), null)); //$NON-NLS-1$
		}
	}

	/**
	 * @see org.eclipse.help.internal.appserver.IWebappServer#start(java.lang.String,
	 *      org.eclipse.core.runtime.IPath, java.lang.ClassLoader)
	 */
	public void start(String webappName, IPath path, ClassLoader customLoader)
			throws CoreException {

		if (!isStarted) {
			start(port, hostAddress);
		}
		if (!running) {
			throw new CoreException(new Status(IStatus.ERROR,
					TomcatPlugin.PLUGIN_ID, IStatus.OK, TomcatResources
							.getString("TomcatAppServer.addingWebapp", //$NON-NLS-1$
									webappName, path.toOSString()), null));
		}

		String contextPath = webappName;
		if (!contextPath.startsWith("/")) { //$NON-NLS-1$
			contextPath = "/" + contextPath; //$NON-NLS-1$
		}
		try {
			Context context = embedded.createContext(contextPath, path
					.toOSString());
			if (context instanceof StandardContext) {
				((StandardContext) context)
						.setWorkDir(getWorkingDirectory(webappName));
			}

			WebAppClassLoader webappLoader = new WebAppClassLoader(customLoader);
			context.setLoader(embedded.createLoader(webappLoader));

			host.addChild(context);

		} catch (Exception exc) {
			throw new CoreException(new Status(IStatus.ERROR,
					TomcatPlugin.PLUGIN_ID, IStatus.OK, TomcatResources
							.getString("TomcatAppServer.addingWebapp", //$NON-NLS-1$
									webappName, path.toOSString()), exc));
		}
	}

	/**
	 * @see org.eclipse.help.internal.appserver.IWebappServer#stop(java.lang.String)
	 */
	public void stop(String webappName) throws CoreException {
		if (!running) {
			return;
		}
		Context context = (Context) host.findChild("/" + webappName); //$NON-NLS-1$
		if (context != null) {
			embedded.removeContext(context);
		}
	}

	/**
	 * @see org.eclipse.help.internal.appserver.IWebappServer#getHost()
	 */
	public String getHost() {
		if (!running) {
			return null;
		}
		return hostAddress;
	}

	/**
	 * @see org.eclipse.help.internal.appserver.IWebappServer#getPort()
	 */
	public int getPort() {
		if (!running) {
			return 0;
		}
		return port;
	}

	/**
	 * @see org.eclipse.help.internal.appserver.IWebappServer#isRunning()
	 */
	public boolean isRunning() {
		return running;
	}

	/**
	 * @see org.eclipse.help.internal.appserver.IWebappServer#stop()
	 */
	public void stop() throws CoreException {
		if (!running) {
			return;
		}
		running = false;

		// Remove the engine (which should trigger removing the connector)
		try {
			embedded.removeEngine(engine);
		} catch (Exception exc) {
			throw new CoreException(new Status(IStatus.ERROR,
					TomcatPlugin.PLUGIN_ID, IStatus.OK, TomcatResources
							.getString("TomcatAppServer.engineRemove"), //$NON-NLS-1$
					exc));
		}
		// Shut down this tomcat server (should have nothing left to do)
		try {
			embedded.stop();
		} catch (LifecycleException e) {
			throw new CoreException(new Status(IStatus.ERROR,
					TomcatPlugin.PLUGIN_ID, IStatus.OK, TomcatResources
							.getString("TomcatAppServer.embeddedStop"), //$NON-NLS-1$
					e));
		}
	}

	private String getWorkingDirectory(String webApp) {
		return TomcatPlugin.getDefault().getStateLocation().append(webApp)
				.toOSString();
	}
}
