Java tutorial
/* * Adito * * Copyright (C) 2003-2006 3SP LTD. All Rights Reserved * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package com.adito.server; import com.adito.boot.BootProgressMonitor; import com.adito.boot.Branding; import com.adito.boot.BrowserLauncher; import com.adito.boot.Context; import com.adito.boot.ContextConfig; import com.adito.boot.ContextHolder; import com.adito.boot.ContextKey; import com.adito.boot.ContextListener; import com.adito.boot.DefaultPropertyDefinition; import com.adito.boot.KeyStoreManager; import com.adito.boot.LogBootProgressMonitor; import com.adito.boot.PropertyClass; import com.adito.boot.PropertyClassManager; import com.adito.boot.PropertyDefinition; import com.adito.boot.PropertyList; import com.adito.boot.PropertyPreferences; import com.adito.boot.RequestHandler; import com.adito.boot.RequestHandlerRequest; import com.adito.boot.RequestHandlerResponse; import com.adito.boot.AditoServerFactory; import com.adito.boot.StopContextListenerThread; import com.adito.boot.SystemProperties; import com.adito.boot.Util; import com.adito.boot.VersionInfo; import com.adito.boot.VersionInfo.Version; import com.adito.boot.XMLPropertyDefinition; import com.adito.server.jetty.CustomHttpContext; import com.adito.server.jetty.CustomJsseListener; import com.adito.server.jetty.CustomWebApplicationContext; import com.adito.server.jetty.HTTPRedirectHandler; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.Authenticator; import java.net.Inet4Address; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.NetworkInterface; import java.net.Socket; import java.net.SocketException; import java.net.URL; import java.net.URLClassLoader; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import javax.net.ssl.TrustManager; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import org.mortbay.http.HttpContext; import org.mortbay.http.JsseListener; import org.mortbay.http.NCSARequestLog; import org.mortbay.http.ResourceCache; import org.mortbay.http.SocketListener; import org.mortbay.http.handler.MsieSslHandler; import org.mortbay.jetty.Server; import org.mortbay.jetty.servlet.ServletHandler; import org.mortbay.jetty.servlet.ServletHttpRequest; import org.mortbay.jetty.servlet.ServletHttpResponse; import org.mortbay.jetty.servlet.SessionManager; import org.mortbay.util.LifeCycleEvent; import org.mortbay.util.LifeCycleListener; import org.mortbay.util.Password; import org.tanukisoftware.wrapper.WrapperListener; import org.tanukisoftware.wrapper.WrapperManager; /** * <p> * Provides an entry point and a default environment for starting the Adito * service. * * <p> * Adito is primarily a standard Java web application. However, it requires a * few additional services from the container that it is running in. This * environment is called the {@link com.adito.boot.Context} (see this interfaces * Javadoc for more information about this environment) and this class * implements that interface. * * <p> * This class currently provides an implementation that uses Jetty for the * servlet / JSP container. * * <p> * The <i>Context Properties</b> are stored using the Java Preferences API so * will likely end up in the Windows register on Win32 platforms or XML files * everywhere else. * * @see com.adito.boot.Context */ public class DefaultAditoServerFactory implements AditoServerFactory, WrapperListener, Context { private static final Log LOG = LogFactory.getLog(DefaultAditoServerFactory.class); private static File DB_DIR = new File("db"); private static File CONF_DIR = new File("conf"); private static File TMP_DIR = new File("tmp"); private static final File LOG_DIR = new File("logs"); private static File appDir = null; private static Preferences PREF; // Private instance variables private ClassLoader bootLoader; private Server server; private long startupStarted; private HashMap<URL, ResourceCache> resourceCaches; private String hostAddress; private static boolean useWrapper = false; private boolean install; private boolean gui; private Throwable startupException; // a list of listeners holding the http and https socket listeners. private List<SocketListener> listeners; private CustomWebApplicationContext webappContext; private CustomHttpContext httpContext; private String jettyLog; private int defaultPort; private int actualPort; private boolean useDevConfig; private String hostname; private ServerLock serverLock; private Thread mainThread; private Thread insecureThread; private ThreadGroup threadGroup; private TreeMap<String, PropertyDefinition> contextPropertyDefinitions; private List<ContextListener> contextListeners; private boolean shuttingDown; private ContextConfig contextConfiguration; private BootProgressMonitor bootProgressMonitor; private boolean logToConsole; private boolean restarting; private Server insecureServer; private ServletHandler servletHandler; public void createServer(final ClassLoader bootLoader, final String[] args) { // This is a hack to allow the Install4J installer to get the java // runtime that will be used if (args.length > 0 && args[0].equals("--jvmdir")) { System.out.println(SystemProperties.get("java.home")); System.exit(0); } this.bootLoader = bootLoader; useWrapper = System.getProperty("wrapper.key") != null; ContextHolder.setContext(this); if (useWrapper) { WrapperManager.start(this, args); } else { Integer returnCode = start(args); if (returnCode != null) { if (gui) { if (startupException == null) { startupException = new Exception("An exit code of " + returnCode + " was returned."); } try { if (SystemProperties.get("os.name").toLowerCase().startsWith("windows")) { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } } catch (ClassNotFoundException e) { } catch (InstantiationException e) { } catch (IllegalAccessException e) { } catch (UnsupportedLookAndFeelException e) { } String mesg = startupException.getMessage() == null ? "No message supplied." : startupException.getMessage(); StringBuilder buf = new StringBuilder(); int l = 0; char ch; for (int i = 0; i < mesg.length(); i++) { ch = mesg.charAt(i); if (l > 50 && ch == ' ') { buf.append("\n"); l = 0; } else { if (ch == '\n') { l = 0; } else { l++; } buf.append(ch); } } mesg = buf.toString(); final String fMesg = mesg; try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { JOptionPane.showMessageDialog(null, fMesg, "Startup Error", JOptionPane.ERROR_MESSAGE); } }); } catch (InterruptedException ex) { } catch (InvocationTargetException ex) { } } System.exit(returnCode); } else { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { if (!shuttingDown) { DefaultAditoServerFactory.this.stop(0); } } }); } } } public Integer start(String[] args) { startupStarted = System.currentTimeMillis(); // Inform the wrapper the startup process may take a while if (useWrapper) { WrapperManager.signalStarting(60000); } // Parse the command line Integer returnCode = parseCommandLine(args); if (returnCode != null) { if (returnCode == 999) { return null; } return returnCode; } // Create the boot progress monitor if (gui) { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException e) { } catch (InstantiationException e) { } catch (IllegalAccessException e) { } catch (UnsupportedLookAndFeelException e) { } bootProgressMonitor = new SwingBootProgressMonitor(); } else { bootProgressMonitor = new LogBootProgressMonitor(); } // resourceCaches = new HashMap<URL, ResourceCache>(); contextListeners = new ArrayList<ContextListener>(); loadSystemProperties(); initialiseLogging(); /* * Migrate preferences. */ File newPrefDir = new File(ContextHolder.getContext().getConfDirectory(), "prefs"); PREF = PropertyPreferences.SYSTEM_ROOT; try { if (!newPrefDir.exists() && Preferences.systemRoot().node("/com").nodeExists("adito")) { Preferences from = Preferences.systemRoot().node("/com/adito"); LOG.warn("Migrating preferences"); try { copyNode(from.node("core"), PREF.node("core")); from.node("core").removeNode(); copyNode(from.node("plugin"), PREF.node("plugin")); from.node("plugin").removeNode(); copyNode(from.node("extensions"), PREF.node("extensions")); from.node("extensions").removeNode(); copyNode(from.node("dbupgrader"), PREF.node("dbupgrader")); from.node("dbupgrader").removeNode(); } catch (BackingStoreException e) { LOG.error("Failed to migrate preferences.", e); } try { from.flush(); } catch (BackingStoreException bse) { LOG.error("Failed to flush old preferences"); } try { PREF.flush(); } catch (BackingStoreException bse) { LOG.error("Failed to flush new preferences"); } if (LOG.isInfoEnabled()) { LOG.info("Flushing preferences"); } } } catch (BackingStoreException bse) { LOG.error("Failed to migrate preferences.", bse); } // Inform the wrapper the startup process is going ok if (useWrapper) { WrapperManager.signalStarting(60000); } try { clearTemp(); try { hostname = Inet4Address.getLocalHost().getCanonicalHostName(); hostAddress = Inet4Address.getLocalHost().getHostAddress(); } catch (UnknownHostException ex) { // This should be fatal, we now rely on the hostname being // available throw new Exception("The host name or address on which this service is running could not " + "be determined. Check you network configuration. One possible cause is " + "a misconfigured 'hosts' file (e.g. on UNIX-like systems this would be " + "/etc/hosts, on Windows XP it would be " + "C:\\Windows\\System32\\Drivers\\Etc\\Hosts)."); } PropertyClassManager.getInstance() .registerPropertyClass(contextConfiguration = new ContextConfig(getClass().getClassLoader())); // Display some information about the system we are running on displaySystemInfo(); // Load the context property definitions loadContextProperties(); // Inform the wrapper the startup process is going ok if (useWrapper) { WrapperManager.signalStarting(60000); } // Configure any HTTP / HTTPS / SOCKS proxy servers configureProxyServers(); PropertyList l = contextConfiguration.retrievePropertyList(new ContextKey("webServer.bindAddress")); getBootProgressMonitor().updateMessage("Creating server lock"); getBootProgressMonitor().updateProgress(6); serverLock = new ServerLock(l.get(0)); if (serverLock.isLocked()) { if (!isSetupMode()) { if (serverLock.isSetup()) { throw new Exception("The installation wizard is currently running. " + "Please shut this down by pointing your browser " + "to http://" + getHostname() + ":" + serverLock.getPort() + "/showShutdown.do before attempting to start the server again."); } else { throw new Exception("The server is already running."); } } else { if (!serverLock.isSetup()) { throw new Exception("The server is currently already running. " + "Please shut this down by pointing your browser " + "to https://" + getHostname() + ":" + serverLock.getPort() + "/showShutdown.do before attempting to start the server again."); } else { throw new Exception("The installation wizard is running.."); } } } // Inform the wrapper the startup process is going ok if (useWrapper) { WrapperManager.signalStarting(60000); } Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { serverLock.stop(); } }); // registerKeyStores(); // threadGroup = new ThreadGroup("MainThreadGroup"); if (install) { setupMode(); } else { normalMode(); startHttpServer(); } } catch (Exception t) { startupException = t; LOG.error("Failed to start the server. " + t.getMessage(), t); return 1; } return null; } public PropertyClass getConfig() { return contextConfiguration; } public void controlEvent(int evt) { if (evt == WrapperManager.WRAPPER_CTRL_C_EVENT) { if (LOG.isInfoEnabled()) { LOG.info("Got CTRL+C event"); } WrapperManager.stop(0); } else if (evt == WrapperManager.WRAPPER_CTRL_CLOSE_EVENT) { if (LOG.isInfoEnabled()) { LOG.info("Got windows close event, ignoring."); } } else if (evt == WrapperManager.WRAPPER_CTRL_LOGOFF_EVENT) { if (LOG.isInfoEnabled()) { LOG.info("Got windows logoff event, ignoring."); } } else if (evt == WrapperManager.WRAPPER_CTRL_SHUTDOWN_EVENT) { if (LOG.isInfoEnabled()) { LOG.info("Got shutdown event"); } WrapperManager.stop(0); } else { if (LOG.isInfoEnabled()) { LOG.info("Got unknown control event " + evt + ", ignoring."); } } } public int stop(int exitCode) { if (LOG != null) { if (LOG.isInfoEnabled()) { if (restarting) { LOG.info("Restarting the server."); } else { LOG.info("Shutting down the server."); } } } if (useWrapper) { WrapperManager.signalStopping(20000); } // TODO This really screws up wrapper on windows - no idea why if (server != null && server.isStarted()) { try { server.stop(false); } catch (InterruptedException e) { if (LOG != null) { if (LOG.isInfoEnabled()) { LOG.info("Failed to stop server.", e); } } } } // Inform all context listeners of what is happening for (ContextListener l : contextListeners) { new StopContextListenerThread(l).waitForStop(); } // if (LOG.isInfoEnabled()) { LOG.info("Flushing preferences"); } try { ContextHolder.getContext().getPreferences().flush(); } catch (BackingStoreException bse) { LOG.error("Failed to flush context preferences.", bse); } try { Preferences.systemRoot().flush(); } catch (IllegalStateException ise) { } catch (BackingStoreException bse) { LOG.error("Failed to flush system preferences"); } return exitCode; } void loadContextProperties() throws IOException, JDOMException { getBootProgressMonitor().updateMessage("Loading context properties"); getBootProgressMonitor().updateProgress(4); for (Enumeration<URL> e = getClass().getClassLoader() .getResources("META-INF/contextConfig-definitions.xml"); e.hasMoreElements();) { URL u = e.nextElement(); LOG.info("Loading context property definitions from " + u); SAXBuilder build = new SAXBuilder(); Element root = build.build(u).getRootElement(); if (!root.getName().equals("definitions")) { throw new JDOMException("Root element in " + u + " should be <definitions>"); } for (Iterator i = root.getChildren().iterator(); i.hasNext();) { Element c = (Element) i.next(); if (c.getName().equals("definition")) { DefaultPropertyDefinition def = new XMLPropertyDefinition(c); contextConfiguration.registerPropertyDefinition(def); } else { throw new JDOMException( "Expect root element of <definitions> with child elements of <definition>. Got <" + c.getName() + ">."); } } } } void copyNode(Preferences from, Preferences to) throws BackingStoreException { String[] keys = from.keys(); for (String key : keys) { to.put(key, from.get(key, "")); } String childNodes[] = from.childrenNames(); for (String childNode : childNodes) { Preferences cn = from.node(childNode); Preferences tn = to.node(childNode); copyNode(cn, tn); } } public boolean isSetupMode() { return install; } public boolean isRestartAvailableMode() { return (useDevConfig || useWrapper) && !isSetupMode(); } public void shutdown(boolean restart) { shuttingDown = true; restarting = restart; if (useWrapper) { if (restart) { WrapperManager.restart(); } else { WrapperManager.stop(0); } } else { stop(0); System.exit(0); } } public Version getVersion() { return VersionInfo.getVersion(); } public File getConfDirectory() { return CONF_DIR; } public File getTempDirectory() { return TMP_DIR; } public File getLogDirectory() { return LOG_DIR; } public File getApplicationDirectory() { return appDir == null ? new File(getTempDirectory(), "extensions") : appDir; } public File getDBDirectory() { return DB_DIR; } public Thread getMainThread() { return mainThread; } public void setResourceAlias(String uri, String location) { webappContext.setResourceAlias(uri, location); } public void addResourceBase(URL base) { if (LOG.isInfoEnabled()) { LOG.info("Adding new resource base " + base.toExternalForm()); } ResourceCache cache = new ResourceCache(); cache.setMimeMap(webappContext.getMimeMap()); cache.setEncodingMap(webappContext.getEncodingMap()); cache.setResourceBase(base.toExternalForm()); try { cache.start(); webappContext.addResourceCache(cache); if (httpContext != null) { httpContext.addResourceCache(cache); } resourceCaches.put(base, cache); } catch (Exception e) { LOG.error("Failed to add new resource base " + base.toExternalForm() + ".", e); } } public void removeResourceBase(URL base) { if (LOG.isInfoEnabled()) { LOG.info("Removing resource base " + base.toExternalForm()); } ResourceCache cache = resourceCaches.get(base); webappContext.removeResourceCache(cache); if (httpContext != null) { httpContext.removeResourceCache(cache); } } public String getHostname() { return hostname; } public int getPort() { return actualPort; } public void addContextLoaderURL(URL url) { doAddContextLoaderURL(url); } public void registerRequestHandler(RequestHandler requestHandler) { registerRequestHandler(requestHandler, HandlerProtocol.HTTPS_PROTOCOL); } public void deregisterRequestHandler(RequestHandler requestHandler) { if (httpContext != null) { httpContext.deregisterRequestHandler(requestHandler); } HTTPRedirectHandler.registerHandler(requestHandler); } private void registerKeyStores() throws Exception { getBootProgressMonitor().updateMessage("Registering key stores"); getBootProgressMonitor().updateProgress(7); String defaultKeyStorePassword = contextConfiguration .retrieveProperty(new ContextKey("webServer.keystore.sslCertificate.password")); KeyStoreManager.registerKeyStore(KeyStoreManager.DEFAULT_KEY_STORE, "keystore", true, defaultKeyStorePassword, KeyStoreManager.getKeyStoreType( contextConfiguration.retrieveProperty(new ContextKey("webServer.keyStoreType")))); KeyStoreManager.registerKeyStore(KeyStoreManager.SERVER_AUTHENTICATION_CERTIFICATES_KEY_STORE, "keystore", true, "adito", KeyStoreManager.TYPE_JKS); KeyStoreManager.registerKeyStore(KeyStoreManager.TRUSTED_SERVER_CERTIFICATES_KEY_STORE, "keystore", true, "adito", KeyStoreManager.TYPE_JKS); } private void clearTemp() { String currVer = PREF.get("lastTempClear", ""); if (currVer.length() == 0 || !currVer.equals(getVersion().toString()) || "true".equalsIgnoreCase(SystemProperties.get("adito.clearTemp"))) { getBootProgressMonitor().updateMessage("Clearing temporary files"); getBootProgressMonitor().updateProgress(3); if (LOG.isInfoEnabled()) { LOG.info("Clearing temporary directory"); } /* We have to leave the server.run and server.pid files alone as these are * used by external components to determine service state */ File[] files = getTempDirectory().listFiles(); if (files != null) { for (File file : files) { if (!file.getName().equals(ServerLock.LOCK_NAME) && !file.getName().equals(Branding.SERVICE_NAME + ".pid")) { Util.delTree(file); } } } else { if (!getTempDirectory().mkdirs()) { LOG.error("CRITICAL. Failed to create the temporary directory " + getTempDirectory() + "."); } } } PREF.put("lastTempClear", getVersion().toString()); } private void initialiseLogging() { URL resource = bootLoader.getResource("log4j.properties"); getBootProgressMonitor().updateMessage("Intialising logging"); getBootProgressMonitor().updateProgress(2); LOG_DIR.mkdirs(); InputStream in = null; try { if (resource == null) { throw new IOException("Could not locate log4j.properties"); } in = resource.openStream(); Properties p = new Properties(); p.load(in); p.setProperty("log4j.rootCategory", p.getProperty("log4j.rootCategory", "WARN,logfile") + (logToConsole ? ",stdout" : "")); Class.forName("org.apache.log4j.PropertyConfigurator", true, bootLoader) .getMethod("configure", new Class[] { Properties.class }).invoke(null, new Object[] { p }); } catch (IOException e) { } catch (ClassNotFoundException e) { } catch (IllegalAccessException e) { } catch (IllegalArgumentException e) { } catch (NoSuchMethodException e) { } catch (SecurityException e) { } catch (InvocationTargetException e) { } finally { Util.closeStream(in); } } private void startHttpServer() throws Exception { int port = contextConfiguration.retrievePropertyInt(new ContextKey("webServer.httpRedirectPort")); if (port <= 0) { if (LOG.isInfoEnabled()) { LOG.info("HTTP redirect port " + port + " is invalid"); } return; } String bind = contextConfiguration.retrieveProperty(new ContextKey("webServer.bindAddress")); PropertyList l = new PropertyList(bind.length() == 0 ? "0.0.0.0" : bind); insecureServer = new Server(); for (String address : l) { if (LOG.isInfoEnabled()) { LOG.info("Adding listener on " + address + ":" + port); } SocketListener listener = new SocketListener(); listener.setHost(address); listener.setPort(port); listener.setMinThreads( contextConfiguration.retrievePropertyInt(new ContextKey("webServer.minThreads"))); listener.setMaxThreads( contextConfiguration.retrievePropertyInt(new ContextKey("webServer.maxThreads"))); listener.setMaxIdleTimeMs(0); listener.setLowResourcePersistTimeMs(2000); listener.setAcceptQueueSize(0); listener.setPoolName("P2"); insecureServer.addListener(listener); } // Create the webapp HttpContext context = new HttpContext(); context.setContextPath("/"); context.setResourceBase("./dummy/"); context.addHandler(new HTTPRedirectHandler()); insecureServer.addContext(context); // Configure the server insecureServer.setRequestsPerGC(2000); insecureThread = new Thread(threadGroup, "InsecureWebServer") { @Override public void run() { // Start the server try { insecureServer.start(); } catch (Exception e) { LOG.warn("Failed to start HTTP Jetty. " + e.getMessage(), e); } } }; if (LOG.isInfoEnabled()) { LOG.info("Starting HTTP redirect server"); } insecureThread.start(); } private void normalMode() throws Exception { getBootProgressMonitor().updateMessage("Creating server"); getBootProgressMonitor().updateProgress(8); if (LOG.isInfoEnabled()) { LOG.info("Starting Jetty Web Server"); } server = createServer(); // SunJsseListener listener = new SunJsseListener(); String keystorePassword = contextConfiguration .retrieveProperty(new ContextKey("webServer.keystore.sslCertificate.password")); if (keystorePassword.length() == 0) { throw new Exception( "Private key / certificate password has not been set. Please run the Installation Wizard."); } actualPort = defaultPort == -1 ? contextConfiguration.retrievePropertyInt(new ContextKey("webServer.port")) : defaultPort; String bind = contextConfiguration.retrieveProperty(new ContextKey("webServer.bindAddress")); listeners = new ArrayList<SocketListener>(); PropertyList l = new PropertyList(bind.length() == 0 ? "0.0.0.0" : bind); for (String address : l) { if (LOG.isInfoEnabled()) { LOG.info("Adding listener on " + address + ":" + actualPort); } if (!serverLock.isStarted()) { serverLock.start(actualPort); } SocketListener listener = null; if (contextConfiguration.retrieveProperty(new ContextKey("webServer.protocol")).equals("http")) { listener = new SocketListener(); LOG.warn("The server is configured to listen for plain HTTP connections."); } else { listener = new CustomJsseListener(); MsieSslHandler sslHandler = new MsieSslHandler(); sslHandler.setUserAgentSubString("MSIE 5"); listener.setHttpHandler(sslHandler); } listener.setPort(actualPort); listener.setMinThreads( contextConfiguration.retrievePropertyInt(new ContextKey("webServer.minThreads"))); listener.setMaxThreads( contextConfiguration.retrievePropertyInt(new ContextKey("webServer.maxThreads"))); listener.setMaxIdleTimeMs( contextConfiguration.retrievePropertyInt(new ContextKey("webServer.maxIdleTimeMs"))); listener.setHost(address); listener.setBufferSize( contextConfiguration.retrievePropertyInt(new ContextKey("webServer.bufferSize"))); listener.setBufferReserve( contextConfiguration.retrievePropertyInt(new ContextKey("webServer.bufferReserve"))); listener.setTcpNoDelay( contextConfiguration.retrievePropertyBoolean(new ContextKey("webServer.tcpNoDelay"))); listener.setThreadsPriority( contextConfiguration.retrievePropertyInt(new ContextKey("webServer.threadPriority"))); listeners.add(listener); listener.setLowResourcePersistTimeMs( contextConfiguration.retrievePropertyInt(new ContextKey("webServer.lowResourcePersistTimeMs"))); listener.setPoolName("main"); server.addListener(listener); } // Add the context getBootProgressMonitor().updateMessage("Creating web application"); getBootProgressMonitor().updateProgress(9); httpContext = new CustomHttpContext(server, "/", useDevConfig, bootLoader); httpContext.setRedirectNullPath(false); server.addContext(httpContext); // Dunny servlet handler for faking HttpServletRequest, // HttpServletResponse servletHandler = new ServletHandler(); servletHandler.initialize(httpContext); servletHandler.start(); // Add the webapp webappContext = new CustomWebApplicationContext(useDevConfig, bootLoader); addLifecycleListener(webappContext); server.addContext(webappContext); webappContext.setRedirectNullPath(false); // Configure the server server.setRequestsPerGC( contextConfiguration.retrievePropertyInt(new ContextKey("webServer.requestsPerGC"))); server.setTrace(false); // Set the request log if (contextConfiguration.retrievePropertyBoolean(new ContextKey("webServer.requestLog"))) { NCSARequestLog requestLog = new NCSARequestLog(jettyLog); requestLog.setRetainDays(90); requestLog.setAppend(true); requestLog.setExtended(false); requestLog.setBuffered(false); requestLog.setLogTimeZone("GMT"); server.setRequestLog(requestLog); } // Inform the wrapper the startup process is going ok if (useWrapper) { WrapperManager.signalStarting(60000); } mainThread = new Thread(threadGroup, "WebServer") { @Override public void run() { // Start the server try { server.start(); if (useDevConfig) { LOG.warn("Server startup took " + ((System.currentTimeMillis() - startupStarted) / 1000) + " seconds"); } getBootProgressMonitor().updateMessage("Server is now running"); getBootProgressMonitor().updateProgress(100); Thread.sleep(2000); } catch (Exception e) { LOG.error("Failed to start Jetty. " + e.getMessage(), e); if (useWrapper) { WrapperManager.stop(1); } else { System.exit(1); } } finally { getBootProgressMonitor().dispose(); } } }; mainThread.start(); } public void addWebApp(String path, String warFile) throws Exception { LOG.info("Adding webapp '" + path + "' using path / war '" + warFile + "'"); HttpContext context = server.addWebApplication(path, warFile); context.start(); } private void setupMode() throws Exception { // Ensure that https redirect is not turned on in jetty System.setProperty("jetty.force.HTTPSRedirect", "false"); getBootProgressMonitor().updateMessage("Creating server"); getBootProgressMonitor().updateProgress(8); actualPort = defaultPort == -1 ? 28080 : defaultPort; serverLock.start(actualPort); server = createServer(); SocketListener socketListener = new SocketListener(); socketListener.setPort(actualPort); socketListener.setMinThreads(10); socketListener.setMaxThreads(200); socketListener.setMaxIdleTimeMs(0); socketListener.setLowResourcePersistTimeMs(2000); socketListener.setAcceptQueueSize(0); socketListener.setPoolName("P1"); server.addListener(socketListener); getBootProgressMonitor().updateMessage("Creating web application"); getBootProgressMonitor().updateProgress(9); webappContext = new CustomWebApplicationContext(useDevConfig, bootLoader); webappContext.setRedirectNullPath(false); addLifecycleListener(webappContext); server.addContext(webappContext); // Configure the server server.setRequestsPerGC(2000); String realHostname = hostname == null ? InetAddress.getLocalHost().getHostName() : hostname; /* * If the 'Active DNS' feature is enabled, the DNS server may return the * wild-card name. This will probably fail. As a work-around, if the * hostname looks like a wildcard, then it is simply changed to * 'localhost'. */ if (realHostname.startsWith("*.")) { realHostname = "localhost"; } // final String fRealHostname = realHostname; final int realPort = defaultPort == -1 ? 28080 : defaultPort; // Inform the wrapper the startup process is going ok if (useWrapper) { WrapperManager.signalStarting(60000); } mainThread = new Thread(threadGroup, "WebServer") { @Override public void run() { // Start the server try { server.start(); if (!useWrapper && !"true".equals(SystemProperties.get("adito.noBrowserLaunch"))) { try { BrowserLauncher.openURL("http://" + fRealHostname + ":" + realPort); System.out.println("A browser has been opened and pointed to http://" + fRealHostname + ":" + realPort + ". "); } catch (IOException ex) { System.out.println( "Point your browser to http://" + fRealHostname + ":" + realPort + ". "); } } else { System.out.println("Point your browser to http://" + fRealHostname + ":" + realPort + ". "); } System.out.println( "\nPress CTRL+C or use the 'Shutdown' option from the web interface to leave the installation wizard."); getBootProgressMonitor().updateMessage("Server is now running"); getBootProgressMonitor().updateProgress(100); Thread.sleep(2000); } catch (Exception e) { LOG.error("Failed to start Jetty. " + e.getMessage(), e); if (useWrapper) { WrapperManager.stop(1); } else { System.exit(1); } } finally { getBootProgressMonitor().dispose(); } } }; System.out.print("Starting installation wizard"); mainThread.start(); /* * Wait for up to 5 minutes for the server to become available we need * to wait this long because precompilation can take a while! */ int waitFor = 60 * 5; boolean running = false; if (!"true".equals(SystemProperties.get("adito.disableStartupCheck", "false"))) { int i = 0; for (; i < waitFor && !running; i++) { try { System.out.print("."); Socket s = new Socket(realHostname, realPort); s.close(); running = true; } catch (IOException ex) { try { Thread.sleep(1000); } catch (InterruptedException ex2) { } } } System.out.println(); } else { running = true; } if (!running) { System.out.println("Failed to start installation wizard. Check the logs for more detail."); if (useWrapper) { WrapperManager.stop(1); } else { System.exit(1); } } } private void configureProxyServers() throws Exception { getBootProgressMonitor().updateMessage("Configuring proxy servers"); getBootProgressMonitor().updateProgress(5); String httpProxyHost = contextConfiguration.retrieveProperty(new ContextKey("proxies.http.proxyHost")); if (httpProxyHost.length() != 0) { if (LOG.isInfoEnabled()) { LOG.info("Configuring outgoing HTTP connections to use a proxy server."); } System.setProperty("http.proxyHost", httpProxyHost); System.setProperty("com.maverick.ssl.https.HTTPProxyHostname", httpProxyHost); String httpProxyPort = contextConfiguration.retrieveProperty(new ContextKey("proxies.http.proxyPort")); String httpProxyUsername = contextConfiguration .retrieveProperty(new ContextKey("proxies.http.proxyUser")); String httpProxyPassword = contextConfiguration .retrieveProperty(new ContextKey("proxies.http.proxyPassword")); System.setProperty("http.proxyPort", httpProxyPort); System.setProperty("com.maverick.ssl.https.HTTPProxyPort", httpProxyPort); if (httpProxyUsername.trim().length() != 0) { System.setProperty("com.maverick.ssl.https.HTTPProxyUsername", httpProxyUsername.trim()); } if (httpProxyPassword.trim().length() != 0) { System.setProperty("com.maverick.ssl.https.HTTPProxyPassword", httpProxyPassword.trim()); } System.setProperty("com.maverick.ssl.https.HTTPProxySecure", "false"); PropertyList list = contextConfiguration .retrievePropertyList(new ContextKey("proxies.http.nonProxyHosts")); StringBuilder hosts = new StringBuilder(); for (Iterator i = list.iterator(); i.hasNext();) { if (hosts.length() != 0) { hosts.append("|"); } hosts.append(i.next()); } System.setProperty("http.nonProxyHosts", hosts.toString()); System.setProperty("com.maverick.ssl.https.HTTPProxyNonProxyHosts", hosts.toString()); } String socksProxyHost = contextConfiguration.retrieveProperty(new ContextKey("proxies.socksProxyHost")); if (socksProxyHost.length() != 0) { if (LOG.isInfoEnabled()) { LOG.info("Configuring outgoing TCP/IP connections to use a SOCKS proxy server."); } System.setProperty("socksProxyHost", httpProxyHost); System.setProperty("socksProxyPort", contextConfiguration.retrieveProperty(new ContextKey("proxies.socksProxyPort"))); } if (socksProxyHost.length() != 0 || httpProxyHost.length() != 0) { Authenticator.setDefault(new ProxyAuthenticator()); } } private Server createServer() throws MalformedURLException { Server result = new Server(); if (contextConfiguration.retrievePropertyBoolean(new ContextKey("webServer.stats"))) { new StatsLogger(result, contextConfiguration.retrievePropertyInt(new ContextKey("webServer.statsUpdate"))); } return result; } private void addLifecycleListener(final CustomWebApplicationContext context) { context.addEventListener(new LifeCycleListener() { public void lifeCycleFailure(LifeCycleEvent arg0) { } public void lifeCycleStarted(LifeCycleEvent arg0) { getBootProgressMonitor().updateMessage("Server is now running"); getBootProgressMonitor().updateProgress(100); try { Thread.sleep(500); } catch (InterruptedException e) { } } public void lifeCycleStarting(LifeCycleEvent arg0) { } public void lifeCycleStopped(LifeCycleEvent arg0) { } public void lifeCycleStopping(LifeCycleEvent arg0) { } }); } private void displaySystemInfo() throws SocketException { if (useDevConfig) { LOG.warn("Development environment enabled. Do not use this on a production server."); } if (LOG.isInfoEnabled()) { LOG.info("Version is " + ContextHolder.getContext().getVersion()); LOG.info("Java version is " + SystemProperties.get("java.version")); LOG.info("Server is installed on " + hostname + "/" + hostAddress); LOG.info("Configuration: " + CONF_DIR.getAbsolutePath()); } if (SystemProperties.get("java.vm.name", "").indexOf("GNU") > -1 || SystemProperties.get("java.vm.name", "").indexOf("libgcj") > -1) { System.out.println("********** WARNING **********"); System.out.println("The system has detected that the Java runtime is GNU/GCJ"); System.out.println("Adito does not work correctly with this Java runtime"); System.out.println("you should reconfigure with a different runtime"); System.out.println("*****************************"); LOG.warn("********** WARNING **********"); LOG.warn("The system has detected that the Java runtime is GNU/GCJ"); LOG.warn("Adito may not work correctly with this Java runtime"); LOG.warn("you should reconfigure with a different runtime"); LOG.warn("*****************************"); } Enumeration e = NetworkInterface.getNetworkInterfaces(); while (e.hasMoreElements()) { NetworkInterface netface = (NetworkInterface) e.nextElement(); if (LOG.isInfoEnabled()) { LOG.info("Net interface: " + netface.getName()); } Enumeration e2 = netface.getInetAddresses(); while (e2.hasMoreElements()) { InetAddress ip = (InetAddress) e2.nextElement(); if (LOG.isInfoEnabled()) { LOG.info("IP address: " + ip.toString()); } } } if (LOG.isInfoEnabled()) { LOG.info("System properties follow:"); } Properties sysProps = System.getProperties(); for (Map.Entry entry : sysProps.entrySet()) { int idx = 0; String val = (String) entry.getValue(); while (true) { if (entry.getKey().equals("java.class.path")) { StringTokenizer t = new StringTokenizer(entry.getValue().toString(), SystemProperties.get("path.separator", ",")); while (t.hasMoreTokens()) { String s = t.nextToken(); if (LOG.isInfoEnabled()) { LOG.info("java.class.path=" + s); } } break; } else { if ((val.length() - idx) > 256) { if (LOG.isInfoEnabled()) { LOG.info(" " + entry.getKey() + "=" + val.substring(idx, idx + 256)); } idx += 256; } else { if (LOG.isInfoEnabled()) { LOG.info(" " + entry.getKey() + "=" + val.substring(idx)); } break; } } } } } private void loadSystemProperties() { getBootProgressMonitor().updateMessage("Loading system properties"); getBootProgressMonitor().updateProgress(1); /* * Read in system properties from a resource, more a debugging aid than * anything else */ InputStream in = null; try { File f = new File(CONF_DIR, "system.properties"); in = new FileInputStream(f); Properties p = new Properties(); p.load(in); for (Enumeration e = p.keys(); e.hasMoreElements();) { String k = (String) e.nextElement(); System.getProperties().setProperty(k, p.getProperty(k).trim()); } } catch (IOException e) { // Dont care } finally { if (in != null) { try { in.close(); } catch (IOException ioe) { } } } /** * Set the prefix if any. */ SystemProperties.setPrefix(System.getProperty("boot.propertyPrefix")); /** * Are we in development mode? */ useDevConfig = "true".equalsIgnoreCase(SystemProperties.get("adito.useDevConfig")); if (!"".equals(SystemProperties.get("adito.extensions", ""))) { appDir = new File(SystemProperties.get("adito.extensions")); } // System.setProperty("org.mortbay.jetty.servlet.SessionCookie", SystemProperties.get("adito.cookie", "JSESSIONID")); System.setProperty("org.mortbay.jetty.servlet.SessionURL", SystemProperties.get("adito.cookie", "JSESSIONID").toLowerCase()); } private Integer parseCommandLine(String[] args) { defaultPort = -1; logToConsole = false; jettyLog = "logs/yyyy_mm_dd.request.log"; boolean fullReset = false; String os = System.getProperty("os.name").toLowerCase(); gui = "true".equals(System.getProperty("adito.useDevConfig")) && os.startsWith("windows"); try { for (String arg : args) { if (arg.equals("--manager")) { System.err.println( "The database manager can no longer be started via the server command line. Run it directly from hsqldb.jar ensuring you have the webapp classes included in your class path."); return new Integer(1); } else if (arg.equals("--setup")) { System.err.println("WARNING: --setup is deprecated, please use --install"); install = true; } else if (arg.equals("--install")) { install = true; } else if (arg.equals("--logToConsole")) { logToConsole = true; } else if (arg.equals("--gui")) { if (os.startsWith("windows")) { gui = true; } else if (os.equals("linux") || os.equals("solaris") || os.endsWith("aix")) { String displaySysProp = SystemProperties.get("adito.display", ""); String display = null; try { display = displaySysProp.length() == 0 ? System.getenv("DISPLAY") : displaySysProp; } catch (Throwable t) { } gui = display != null && display.length() > 0; } } else if (arg.startsWith("--db")) { DB_DIR = new File(arg.substring(5)); if (DB_DIR.exists() && !DB_DIR.isDirectory()) { throw new Exception( "--db option specifies an existing file, must either not exist or be a directory"); } } else if (arg.startsWith("--applications")) { appDir = new File(arg.substring(15)); if (appDir.exists() && !appDir.isDirectory()) { throw new Exception( "--db option specifies an existing file, must either not exist or be a directory"); } } else if (arg.startsWith("--temp")) { TMP_DIR = new File(arg.substring(7)); if (TMP_DIR.exists() && !TMP_DIR.isDirectory()) { throw new Exception( "--temp option specifies an existing file, must either not exist or be a directory"); } } else if (arg.startsWith("--conf")) { CONF_DIR = new File(arg.substring(7)); if (!CONF_DIR.exists() || !CONF_DIR.isDirectory()) { throw new Exception("--conf option does not specify a valid directory"); } } else if (arg.startsWith("--port")) { defaultPort = Integer.parseInt(arg.substring(7)); } else if (arg.startsWith("--jettyLog")) { jettyLog = arg.substring(11); } else if (arg.equals("--full-reset")) { fullReset = true; } else if (arg.startsWith("start")) { } else { System.err.println("Starts / configures the server.\n"); System.err.println("Usage: adito [OPTION]..."); System.err.println("\nThe server may be started in setup or normal mode. When setup.\n"); System.err.println("mode is enabled a plain http server will be started on port 28080\n"); System.err.println("allowing you configure using a browser.\n\n"); System.out.println("Options:\n"); System.out.println(" --install Start the server in installation mode."); System.out.println(" --full-reset Deletes *all* configuration data and resets"); System.out.println(" the server to its initial state. Use with"); System.out.println(" greate caution."); System.out.println(" --db=DIR Set the directory where the configuration"); System.out.println(" database is stored."); System.out.println(" --conf=DIR Set the directory where the configuration"); System.out.println(" files are stored."); System.out.println(" --temp=DIR Set the directory where the temporary"); System.out.println(" files are stored."); System.out.println(" --port=NUMBER The port on which the server will start."); System.out.println(" Note that this applies to both setup and"); System.out.println(" normal mode and will overide whatever port"); System.out.println(" been configured."); System.out.println(" --jettyLog=LOG The location of the Jetty NCSA request log."); System.out.println("\nInvalid option: " + arg + ".\n"); return 2; } } // Create the temporary directory if (!TMP_DIR.exists()) { if (!TMP_DIR.mkdirs()) { throw new Exception("Could not create temporary directory " + TMP_DIR.getAbsolutePath() + "."); } } } catch (Exception e) { System.err.println(e.getMessage()); return 2; } // Perform a full reset if (fullReset) { if (fullReset()) { System.err.println("Configuration has been fully reset"); return 0; } else { System.err.println("Aborted full reset."); return 1; } } // Another way for external processes to force starting installation // wizard if (new File(TMP_DIR, "setup.run").exists()) { install = true; } return null; } /* * Perform a full reset */ private boolean fullReset() { if (gui) { if (JOptionPane.showConfirmDialog(null, "The embedded configuration database will be\n" + "completely deleted and re-created the next\ntime you run the server. Are you absolutely\n" + "sure you wish to do this?", "Full Reset", JOptionPane.WARNING_MESSAGE, JOptionPane.YES_NO_OPTION) != JOptionPane.OK_OPTION) { return false; } } else { System.out.println("The embedded configuration database will be"); System.out.println("completely deleted and re-created the next"); System.out.println("time you run the server. Are you absolutely"); System.out.println("sure you wish to do this?"); System.out.println(); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); while (true) { System.out.print("(Y)es or (N)o: "); String s; try { s = br.readLine(); if (s == null) { return false; } if (s.toLowerCase().equals("y") || s.toLowerCase().equals("yes")) { break; } if (s.toLowerCase().equals("n") || s.toLowerCase().equals("no")) { return false; } System.out.println( "\nPlease answer 'y' or 'yes' to perform the reset, or 'n' or 'no' to abort the reset."); } catch (IOException e) { return false; } } } // Start the reset System.out.println("Resetting all configuration"); File[] f = getDBDirectory().listFiles(); if (f != null) { for (File f1 : f) { System.out.println(" Deleting " + f1.getPath()); if (!f1.delete()) { System.out.println(" Failed to remove"); } } } return true; } public String deobfuscatePassword(String val) { try { return Password.deobfuscate(val); } catch (Exception e) { return ""; } } public String obfuscatePassword(String val) { return Password.obfuscate(val); } public void setTrustManager(TrustManager trustManager, boolean require) { if (listeners == null || listeners.isEmpty()) { LOG.warn("Not setting trust managers there are no SSL listeners configured."); } else { if (LOG.isInfoEnabled()) { LOG.info("Set trust managers"); } for (SocketListener l : listeners) { if (l instanceof CustomJsseListener) { ((JsseListener) l).setNeedClientAuth(trustManager != null); ((CustomJsseListener) l).setTrustManager(trustManager, require); } } } } public void addContextListener(ContextListener contextListener) { contextListeners.add(contextListener); } public void removeContextListener(ContextListener contextListener) { contextListeners.remove(contextListener); } public Preferences getPreferences() { return PREF; } public URL[] getContextLoaderClassPath() { List<URL> urlList = new ArrayList<URL>(); ClassLoader webappContextClassLoader = webappContext.getClassLoader(); while (webappContextClassLoader != null) { if (webappContextClassLoader instanceof URLClassLoader) { urlList.addAll(Arrays.asList(((URLClassLoader) webappContextClassLoader).getURLs())); } webappContextClassLoader = webappContextClassLoader.getParent(); } URL[] urls = urlList.toArray(new URL[urlList.size()]); return urls; } public ClassLoader getContextLoader() { return webappContext.getClassLoader(); } public void removeResourceAlias(String uri) { webappContext.removeResourceAlias(uri); } public Collection<URL> getResourceBases() { return resourceCaches.keySet(); } public BootProgressMonitor getBootProgressMonitor() { return bootProgressMonitor; } public void registerRequestHandler(RequestHandler requestHandler, HandlerProtocol protocol) { if (httpContext != null) { if (protocol == HandlerProtocol.HTTPS_PROTOCOL || protocol == HandlerProtocol.BOTH_PROTOCOLS) { httpContext.registerRequestHandler(requestHandler); } } if (protocol == HandlerProtocol.HTTP_PROTOCOL || protocol == HandlerProtocol.BOTH_PROTOCOLS) { HTTPRedirectHandler.registerHandler(requestHandler); } } public HttpServletRequest createServletRequest(RequestHandlerRequest request) { if (request instanceof com.adito.server.jetty.RequestAdapter) { ServletHttpRequest req = new ServletHttpRequest(servletHandler, request.getPath(), ((com.adito.server.jetty.RequestAdapter) request).getHttpRequest()); return req; } else { throw new IllegalArgumentException("Request must be RequestAdapter"); } } public HttpServletResponse createServletResponse(RequestHandlerResponse response, HttpServletRequest request) { if (response instanceof com.adito.server.jetty.ResponseAdapter) { ServletHttpResponse res = new ServletHttpResponse((ServletHttpRequest) request, ((com.adito.server.jetty.ResponseAdapter) response).getHttpResponse()); request.getSession(true); return res; } else { throw new IllegalArgumentException("Response must be ResponseAdapter"); } } private void doAddContextLoaderURL(URL u) { try { Class sysclass = URLClassLoader.class; Method method = sysclass.getDeclaredMethod("addURL", new Class[] { URL.class }); method.setAccessible(true); method.invoke(webappContext.getClassLoader(), new Object[] { u }); if (LOG.isInfoEnabled()) { LOG.info(u.toExternalForm() + " added to context classloader"); } } catch (NoSuchMethodException e) { LOG.error("Failed to add to classpath.", e); } catch (SecurityException e) { LOG.error("Failed to add to classpath.", e); } catch (IllegalAccessException e) { LOG.error("Failed to add to classpath.", e); } catch (IllegalArgumentException e) { LOG.error("Failed to add to classpath.", e); } catch (InvocationTargetException e) { LOG.error("Failed to add to classpath.", e); } } public void access(HttpSession session) { ((SessionManager.Session) session).access(); } }