axiom.main.Server.java Source code

Java tutorial

Introduction

Here is the source code for axiom.main.Server.java

Source

/*
 * Helma License Notice
 *
 * The contents of this file are subject to the Helma License
 * Version 2.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://adele.helma.org/download/helma/license.txt
 *
 * Copyright 1998-2003 Helma Software. All Rights Reserved.
 *
 * $RCSfile: Server.java,v $
 * $Author: hannes $
 * $Revision: 1.101 $
 * $Date: 2006/05/23 10:47:22 $
 */

/* 
 * Modified by:
 * 
 * Axiom Software Inc., 11480 Commerce Park Drive, Third Floor, Reston, VA 20191 USA
 * email: info@axiomsoftwareinc.com
 */
package axiom.main;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mortbay.util.ByteArrayISO8859Writer;
import org.mortbay.util.MultiException;
import org.mortbay.xml.XmlConfiguration;
import org.mortbay.jetty.Handler;
import org.mortbay.jetty.HttpConnection;
import org.mortbay.jetty.MimeTypes;
import org.mortbay.jetty.NCSARequestLog;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.handler.AbstractHandler;
import org.mortbay.jetty.handler.ContextHandlerCollection;
import org.mortbay.jetty.handler.HandlerCollection;
import org.mortbay.jetty.handler.RequestLogHandler;

import axiom.extensions.AxiomExtension;
import axiom.framework.*;
import axiom.framework.core.*;
import axiom.framework.repository.FileResource;
import axiom.objectmodel.db.DbSource;
import axiom.objectmodel.db.TransSource;
import axiom.util.*;

import java.io.*;
import java.util.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Axiom server main class..
 */
public class Server implements IPathElement, Runnable {

    // version string
    public static final String version = "3.2.10";

    protected ContextHandlerCollection contexts = new ContextHandlerCollection();
    protected HandlerCollection handlers = new HandlerCollection();

    // static server instance
    private static Server server;

    // Server home directory
    protected File axiomHome;

    // server-wide properties
    ResourceProperties dbProps;
    ResourceProperties sysProps;

    // our logger
    private Log logger;
    // are we using axiom.util.Logging?
    private boolean axiomLogging;

    // server start time
    public final long starttime;

    private ApplicationManager appManager;
    private Vector<AxiomExtension> extensions;
    private Thread mainThread;

    String websrvPort = null;

    // map of server-wide database sources
    Hashtable dbSources;

    // the embedded web server
    // protected Serve websrv;
    protected org.mortbay.jetty.Server http;

    Thread shutdownhook;

    private org.h2.tools.Server defaultDbServer;

    /**
     * Constructs a new Server instance with an array of command line options.
     */
    public Server(Config config) {
        server = this;
        starttime = System.currentTimeMillis();
        websrvPort = config.websrvPort;
        if (websrvPort == null) {
            try {
                websrvPort = "8080";
            } catch (Exception ignore) {
            }
        }

        axiomHome = config.homeDir;

        // create system properties
        sysProps = new ResourceProperties();
        sysProps.addResource(new FileResource(config.propFile));
    }

    /**
     *  static main entry point.
     */
    public static void main(String[] args) {
        checkJavaVersion();

        Config config = null;
        try {
            config = getConfig(args);
        } catch (Exception cex) {
            printUsageError("error reading configuration: " + cex.getMessage());
            System.exit(1);
        }

        checkRunning(config);

        // create new server instance
        server = new Server(config);

        // parse properties files etc
        server.init();

        // start the server main thread
        server.start();
    }

    /**
      * check if we are running on a Java 2 VM - otherwise exit with an error message
      */
    public static void checkJavaVersion() {
        String javaVersion = System.getProperty("java.version");

        if ((javaVersion == null) || javaVersion.startsWith("1.4") || javaVersion.startsWith("1.3")
                || javaVersion.startsWith("1.2") || javaVersion.startsWith("1.1")
                || javaVersion.startsWith("1.0")) {
            System.err.println("This version of Axiom requires Java 1.6 or greater.");

            if (javaVersion == null) { // don't think this will ever happen, but you never know
                System.err.println(
                        "Your Java Runtime did not provide a version number. Please update to a more recent version.");
            } else {
                System.err.println("Your Java Runtime is version " + javaVersion
                        + ". Please update to a more recent version.");
            }

            System.exit(1);
        }
    }

    /**
      * parse the command line arguments, read a given server.properties file
      * and check the values given for server ports
      * @return Config if successfull
      * @throws Exception on any configuration error
      */
    public static Config getConfig(String[] args) throws Exception {

        Config config = new Config();

        // get possible environment setting for axiom home
        if (System.getProperty("axiom.home") != null) {
            config.homeDir = new File(System.getProperty("axiom.home"));
        }

        parseArgs(config, args);

        guessConfig(config);

        // create system properties
        ResourceProperties sysProps = new ResourceProperties();
        sysProps.addResource(new FileResource(config.propFile));

        // check if there's a property setting for those ports not specified via command line
        if ((config.websrvPort == null) && (sysProps.getProperty("webPort") != null)) {
            try {
                config.websrvPort = sysProps.getProperty("webPort");
            } catch (Exception portx) {
                throw new Exception("Error parsing web server port property from server.properties: " + portx);
            }
        }

        return config;
    }

    /**
      * parse argument list from command line and store values
      * in given Config object
      * @throws Exception when argument can't be parsed into an InetAddrPort
      * or invalid token is given.
      */
    public static void parseArgs(Config config, String[] args) throws Exception {
        for (int i = 0; i < args.length; i++) {
            if (args[i].equals("-h") && ((i + 1) < args.length)) {
                config.homeDir = new File(args[++i]);
            } else if (args[i].equals("-f") && ((i + 1) < args.length)) {
                config.propFile = new File(args[++i]);
            } else if (args[i].equals("-p") && ((i + 1) < args.length)) {
                ++i;
            } else if (args[i].equals("-x") && ((i + 1) < args.length)) {
                ++i;
            } else if (args[i].equals("-w") && ((i + 1) < args.length)) {
                try {
                    config.websrvPort = args[i + 1];
                    ++i;
                } catch (Exception portx) {
                    throw new Exception("Error parsing web server port property: " + portx);
                }
            } else if (args[i].equals("-jk") && ((i + 1) < args.length)) {
                ++i;
            } else if (args[i].equals("-i") && ((i + 1) < args.length)) {
                // eat away the -i parameter which is meant for axiom.main.launcher.Main
                i++;
            } else {
                throw new Exception("Unknown command line token: " + args[i]);
            }
        }
    }

    /**
      * get main property file from home dir or vice versa,
      * depending on what we have
      */
    public static void guessConfig(Config config) throws Exception {
        // get property file from axiomHome:
        if (config.propFile == null) {
            if (config.homeDir != null) {
                config.propFile = new File(config.homeDir, "server.properties");
            } else {
                config.propFile = new File("server.properties");
            }
        }

        // create system properties
        ResourceProperties sysProps = new ResourceProperties();
        sysProps.addResource(new FileResource(config.propFile));

        // try to get axiomHome from property file
        if (config.homeDir == null && sysProps.getProperty("axiomhome") != null) {
            config.homeDir = new File(sysProps.getProperty("axiomhome"));
        }

        // use the directory where server.properties is located:
        if (config.homeDir == null && config.propFile != null) {
            config.homeDir = config.propFile.getAbsoluteFile().getParentFile();
        }

        if (!config.hasPropFile()) {
            throw new Exception("no server.properties found");
        }

        if (!config.hasHomeDir()) {
            throw new Exception("couldn't determine Axiom directory");
        }

        // try to transform axiomHome directory to its canonical representation
        try {
            config.homeDir = config.homeDir.getCanonicalFile();
        } catch (IOException iox) {
            config.homeDir = config.homeDir.getAbsoluteFile();
        }
    }

    /**
      * print the usage hints and prefix them with a message.
      */
    public static void printUsageError(String msg) {
        System.out.println(msg);
        printUsageError();
    }

    /**
      * print the usage hints
      */
    public static void printUsageError() {
        System.out.println("");
        System.out.println("Usage: java axiom.main.Server [options]");
        System.out.println("Possible options:");
        System.out.println("  -h dir       Specify axiom home directory");
        System.out.println("  -f file      Specify server.properties file");
        System.out.println("  -w [ip:]port      Specify embedded web server address/port");
        System.out.println("  -x [ip:]port      Specify XML-RPC address/port");
        System.out.println("  -jk [ip:]port     Specify AJP13 address/port");
        System.out.println("  -p [ip:]port      Specify RMI address/port");
        System.out.println("");
        System.out.println("Supported formats for server ports:");
        System.out.println("   <port-number>");
        System.out.println("   <ip-address>:<port-number>");
        System.out.println("   <hostname>:<port-number>");
        System.out.println("");
        System.err.println("Usage Error - exiting");
        System.out.println("");
    }

    /**
     *  Check wheter a server is already running on any of the given ports
     *  - otherwise exit with an error message
     */
    public static void checkRunning(Config config) {
        // check if any of the specified server ports is in use already
        try {
            if (config.websrvPort != null) {
                checkPort(config.websrvPort);
            }
        } catch (Exception running) {
            System.out.println(running.getMessage());
            System.exit(1);
        }
    }

    /**
     *  A primitive method to check whether a server is already running on our port.
     */
    private static void checkPort(String addrPort) throws Exception {
        // checkRunning is disabled until we find a fix for the socket creation
        // timeout problems reported on the list.
        return;
    }

    /**
      * initialize the server
      */
    public void init() {

        // set the log factory property
        String logFactory = sysProps.getProperty("loggerFactory", "axiom.util.Logging");

        axiomLogging = "axiom.util.Logging".equals(logFactory);
        // remove comment below to control Jetty Logging, axiom.util.JettyLogger
        // is an implemention of the Jetty Log class, used for logging Jetty error messages
        System.setProperty("org.mortbay.log.class", "axiom.util.JettyLogger");

        System.setProperty("org.apache.commons.logging.LogFactory", logFactory);

        // set the current working directory to the Axiom home dir.
        // note that this is not a real cwd, which is not supported
        // by java. It makes sure relative to absolute path name
        // conversion is done right, so for Axiom code, this should
        // work.
        System.setProperty("user.dir", axiomHome.getPath());

        // from now on it's safe to call getLogger() because axiomHome is set up
        getLogger();

        String startMessage = "Starting Axiom " + version + " on Java " + System.getProperty("java.version");

        logger.info(startMessage);

        // also print a msg to System.out
        System.out.println(startMessage);

        logger.info("Setting Axiom Home to " + axiomHome);

        RequestLogHandler requestLogHandler = new RequestLogHandler();
        handlers.setHandlers(new Handler[] { contexts, new AxiomHandler(), requestLogHandler });
        if (this.sysProps.getProperty("enableRequestLog") == null
                || Boolean.parseBoolean(this.sysProps.getProperty("enableRequestLog"))) {
            String logDir = sysProps.getProperty("logdir", "log");
            String requestLogName = sysProps.getProperty("requestLog", "axiom.server.request.log");
            if (!requestLogName.endsWith(".log")) {
                requestLogName += ".log";
            }
            NCSARequestLog requestLog = new NCSARequestLog(logDir + "/" + requestLogName);
            requestLog.setRetainDays(90);
            requestLog.setAppend(true);
            requestLog.setExtended(true);
            requestLog.setLogServer(true);
            requestLog.setLogTimeZone("GMT");
            requestLogHandler.setRequestLog(requestLog);
        }

        // read db.properties file in Axiom home directory
        dbProps = new ResourceProperties();
        dbProps.setIgnoreCase(false);
        dbProps.addResource(new FileResource(new File(axiomHome, "db.properties")));
        DbSource.setDefaultProps(dbProps);

        String language = sysProps.getProperty("language");
        String country = sysProps.getProperty("country");
        String timezone = sysProps.getProperty("timezone");

        if ((language != null) && (country != null)) {
            Locale.setDefault(new Locale(language, country));
        }

        if (timezone != null) {
            TimeZone.setDefault(TimeZone.getTimeZone(timezone));
        }

        dbSources = new Hashtable();

        // try to load the extensions
        extensions = new Vector<AxiomExtension>();
        initExtensions();
        // start the default DB TCP Server with SSL enabled
        try {
            if (this.isDbHostServer()) {
                String h2port = this.sysProps.getProperty("db.port", "9092");

                String dbhome = this.sysProps.getProperty("dbHome");
                File dir;
                if (dbhome != null) {
                    dir = new File(dbhome);
                } else {
                    dir = new File(this.axiomHome, "db");
                }
                dir = new File(dir, "TransactionsDB");
                String baseDir = dir.getPath();

                System.setProperty("h2.lobFilesPerDirectory", new Long(Long.MAX_VALUE).toString());

                String[] args = new String[] { "-tcpPort", h2port, "-baseDir", baseDir };
                this.defaultDbServer = org.h2.tools.Server.createTcpServer(args).start();
                System.out.println("Starting H2 TCP Server on port " + h2port);
            }
        } catch (Exception sqle) {
            logger.error(ErrorReporter.errorMsg(this.getClass(), "init"), sqle);
            throw new RuntimeException(
                    "FATAL ERROR::Could not start the default " + "H2 database server, " + sqle.getMessage());
        }
    }

    /**
      * initialize extensions
      */
    private void initExtensions() {
        StringBuffer exts = new StringBuffer(sysProps.getProperty("extensions", ""));
        if (exts.length() > 0) {
            exts.append(",");
        }
        exts.append("axiom.extensions.tal.TALExtension");

        StringTokenizer tok = new StringTokenizer(exts.toString(), ",");
        while (tok.hasMoreTokens()) {
            String extClassName = tok.nextToken().trim();

            try {
                Class extClass = Class.forName(extClassName);
                AxiomExtension ext = (AxiomExtension) extClass.newInstance();
                ext.init(this);
                extensions.add(ext);
                logger.info("Loaded: " + extClassName);
            } catch (Throwable e) {
                logger.error("Error loading extension " + extClassName + ": " + e.toString());
            }
        }
    }

    public void start() {
        // Start running, finishing setup and then entering a loop to check changes
        // for new applications
        mainThread = new Thread(this);
        mainThread.start();
    }

    public void stop() {
        mainThread = null;

        getLogger().info("Shutting down Axiom");

        appManager.stopAll();

        if (http != null) {
            try {
                http.stop();
                http.destroy();
            } catch (InterruptedException irx) {
                System.out.println("Exception while trying to stop http obj");
                System.out.println(irx);
                // http.stop() interrupted by another thread. ignore.
            } catch (Exception e) {
                System.out.println(e);
            }
        }

        if (axiomLogging) {
            Logging.shutdown();
        }

        server = null;

        // stop the default DB Server
        if (this.defaultDbServer != null) {
            this.defaultDbServer.stop();
        } else {
            this.shutdownDefaultDb();
        }

        /*try {
        Runtime.getRuntime().removeShutdownHook(shutdownhook);
        // HACK: running the shutdownhook seems to be necessary in order
        // to prevent it from blocking garbage collection of Axiom 
        // classes/classloaders. Since we already set server to null it 
        // won't do anything anyhow.
        shutdownhook.start();
        shutdownhook = null;
        } catch (Exception x) {
           System.out.println("Exception while trying to run shutdownhook thread");
           System.out.println(x);
        // invalid shutdown hook or already shutting down. ignore.
        }*/
    }

    private void shutdownDefaultDb() {
        Object apps[] = getApplications();

        for (int i = 0; i < apps.length; i++) {
            Application app = (Application) apps[i];
            File db = new File(this.getDbHome(), TransSource.TRANSACTIONS_DB_DIR + "_" + app.getName());
            StringBuffer url = new StringBuffer(db.getPath());
            if (!url.toString().endsWith(File.separator)) {
                url.append(File.separator);
            }

            url.append(app.getName() + File.separator + TransSource.TRANSACTIONS_DB_DIR);
            if (!url.toString().endsWith(File.separator)) {
                url.append(File.separator);
            }
            url.append(TransSource.TRANSACTIONS_DB_NAME);

            url.insert(0, TransSource.DEFAULT_URL);

            Connection conn = null;
            PreparedStatement pstmt = null;
            try {
                conn = DriverManager.getConnection(url.toString());
                pstmt = conn.prepareStatement("SHUTDOWN");
                pstmt.execute();
            } catch (Exception ex) {
                // ignore, if shutdown failed, must not be using the default db
            } finally {
                if (pstmt != null) {
                    try {
                        pstmt.close();
                    } catch (SQLException sqle) {
                    }
                    pstmt = null;
                }
                if (conn != null) {
                    try {
                        conn.close();
                    } catch (SQLException sqle) {
                    }
                    conn = null;
                }
            }
        }
    }

    /**
     *  The main method of the Server. Basically, we set up Applications and than
     *  periodically check for changes in the apps.properties file, shutting down
     *  apps or starting new ones.
     */
    public void run() {
        try {
            if ((websrvPort != null)) {
                File f = new File(this.axiomHome, "axiom-config.xml");
                if (f.exists() && f.canRead() && f.isFile()) {
                    try {
                        http = new org.mortbay.jetty.Server();
                        XmlConfiguration config = new XmlConfiguration(f.toURI().toURL());
                        config.configure(http);
                    } catch (Exception ex) {
                        ex.printStackTrace();
                        logger.error(ErrorReporter.errorMsg(this.getClass(), "run"), ex);
                        throw new RuntimeException("Could not setup the web server, errors in axiom-config.xml");
                    }
                } else {
                    http = new org.mortbay.jetty.Server(Integer.valueOf(websrvPort).intValue());

                }
                http.setHandler(handlers);
            }

            appManager = new ApplicationManager(this, 0);

            // add shutdown hook to close running apps and servers on exit
            shutdownhook = new AxiomShutdownHook();
            Runtime.getRuntime().addShutdownHook(shutdownhook);
        } catch (Exception x) {
            logger.error(ErrorReporter.errorMsg(this.getClass(), "run"), x);
            throw new RuntimeException("Error setting up Server", x);
        }
        // set the security manager.
        // the default implementation is axiom.main.AxiomSecurityManager.
        try {
            String secManClass = sysProps.getProperty("securityManager");

            if (secManClass != null) {
                SecurityManager secMan = (SecurityManager) Class.forName(secManClass).newInstance();

                System.setSecurityManager(secMan);
                logger.info("Setting security manager to " + secManClass);
            }
        } catch (Exception x) {
            logger.error(ErrorReporter.errorMsg(this.getClass(), "run"), x);
            logger.error("Error setting security manager", x);
        }

        // start embedded web server
        if (http != null) {
            try {
                http.start();
            } catch (MultiException m) {
                logger.error(ErrorReporter.errorMsg(this.getClass(), "run"), m);
                throw new RuntimeException("Error starting embedded web server", m);
            } catch (Exception e) {
                System.out.println(e);
                logger.error(ErrorReporter.errorMsg(this.getClass(), "run"), e);
                throw new RuntimeException("Error starting embedded web server", e);
            }
        }

        // start applications
        appManager.startAll();

        while (Thread.currentThread() == mainThread) {
            try {
                Thread.sleep(7000L);
            } catch (InterruptedException ie) {
            }

            try {
                appManager.startAll();
            } catch (Exception x) {
                logger.warn("Caught in app manager loop: " + x);
            }
        }

    }

    /**
     * Make sure this server has an ApplicationManager (e.g. used when
     * accessed from CommandlineRunner)
     */
    public void checkAppManager(int port) {
        if (appManager == null) {
            appManager = new ApplicationManager(this, port);
        }
    }

    /**
     *  Get an Iterator over the applications currently running on this Server.
     */
    public Application[] getApplications() {
        return appManager.getApplications();
    }

    /**
     * Get an Application by name
     */
    public Application getApplication(String name) {
        return appManager.getApplication(name);
    }

    /**
     *  Get a logger to use for output in this server.
     */
    public Log getLogger() {
        if (logger == null) {
            if (axiomLogging) {
                // set up system properties for axiom.util.Logging
                String logDir = sysProps.getProperty("logdir", "log");
                if (!"console".equals(logDir)) {
                    // try to get the absolute logdir path
                    File dir = new File(logDir);
                    logDir = dir.getAbsolutePath();
                }
                // set up axiom.logdir system property
                System.setProperty("axiom.logdir", logDir);
            }
            logger = (axiom.util.Logger) LogFactory.getLog("axiom.server");
            ((axiom.util.Logger) logger).setLogLevel(axiom.util.Logger.ERROR);
        }
        return logger;
    }

    /**
     *  Get the Home directory of this server.
     */
    public File getAxiomHome() {
        return axiomHome;
    }

    /**
     * Get the main Server instance.
     */
    public static Server getServer() {
        return server;
    }

    /**
     *
     *
     * @param key ...
     *
     * @return ...
     */
    public String getProperty(String key) {
        return (String) sysProps.get(key);
    }

    /**
     *
     *
     * @return ...
     */
    public ResourceProperties getProperties() {
        return sysProps;
    }

    /**
     *
     *
     * @return ...
     */
    public ResourceProperties getDbProperties() {
        return dbProps;
    }

    public boolean isDbServerTcp() {
        String dbmode = this.sysProps.getProperty("db.mode");
        return (dbmode != null && "TCP_SERVER".equalsIgnoreCase(dbmode));
    }

    public boolean isDbHostServer() {
        return this.isDbServerTcp() && this.getTcpServerHost() == null;
    }

    public String getTcpServerPort() {
        return this.sysProps.getProperty("db.port");
    }

    public String getTcpServerHost() {
        return this.sysProps.getProperty("db.host");
    }

    /**
     *
     *
     * @return ...
     */
    public File getAppsHome() {
        String appHome = sysProps.getProperty("appHome", "");
        if (appHome.trim().length() != 0) {
            File file = new File(appHome);
            if (!file.isAbsolute()) {
                file = new File(axiomHome, appHome);
            }
            return file;
        } else {
            return new File(axiomHome, "apps");
        }
    }

    /**
     *
     *
     * @return ...
     */
    public File getDbHome() {
        String dbHome = sysProps.getProperty("dbHome", "");
        if (dbHome.trim().length() != 0) {
            File file = new File(dbHome);
            if (!file.isAbsolute()) {
                file = new File(axiomHome, dbHome);
            }
            return file;
        } else {
            return new File(axiomHome, "db");
        }
    }

    /**
     *
     *
     * @return ...
     */
    public Vector<AxiomExtension> getExtensions() {
        return extensions;
    }

    /**
     *
     *
     * @param name ...
     */
    public void startApplication(String name) {
        appManager.start(name);
        appManager.register(name);
    }

    /**
     *
     *
     * @param name ...
     */
    public void stopApplication(String name) {
        appManager.stop(name);
    }

    /**
     * method from axiom.framework.IPathElement
     */
    public String getElementName() {
        return "root";
    }

    /**
     * method from axiom.framework.IPathElement,
     * returning active applications
     */
    public IPathElement getChildElement(String name) {
        return appManager.getApplication(name);
    }

    /**
     * method from axiom.framework.IPathElement
     */
    public IPathElement getParentElement() {
        return null;
    }

    /**
     * method from axiom.framework.IPathElement
     */
    public String getPrototype() {
        return "root";
    }

    public ContextHandlerCollection getContexts() {
        return this.contexts;
    }

    public HandlerCollection getHandlers() {
        return handlers;
    }

    public class AxiomHandler extends AbstractHandler {

        private String fileName = "default.html";

        public AxiomHandler() {
        }

        public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch)
                throws IOException, ServletException {
            Request base_request = request instanceof Request ? (Request) request
                    : HttpConnection.getCurrentConnection().getRequest();

            if (response.isCommitted() || base_request.isHandled())
                return;
            base_request.setHandled(true);

            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            response.setContentType(MimeTypes.TEXT_HTML);

            ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(1500);

            if (!appManager.getStarted()) {
                writer.write("<HTML><HEAD><TITLE>axiomstack");
                writer.write("</TITLE><BODY>");
                writer.write("No application on this server matched this request.<br/>");
                writer.write("Please allow axiomstack to finish initializing...<br/>");
                writer.write("</BODY></HTML>");
            } else {
                File f = new File(getAppsHome(), fileName);
                if (f.exists() && f.canRead() && f.isFile()) {
                    FileReader fr = new FileReader(f);
                    int c = 0;
                    while ((c = fr.read()) != -1) {
                        writer.write(c);
                    }
                    fr.close();
                } else {
                    writer.write("<HTML><HEAD><TITLE>axiomstack");
                    writer.write("</TITLE><BODY>");
                    writer.write("No application on this server matched this request.<br/>");
                    writer.write("Known applications are:");
                    writer.write("<ul>");
                    Object apps[] = getApplications();
                    for (int i = 0; i < apps.length; i++) {
                        Application app = (Application) apps[i];
                        writer.write("<li><a href=\"" + app.getBaseURI() + "\">" + app.getName() + "</a></li>");
                    }
                    writer.write("</ul>");
                    writer.write("</BODY></HTML>");
                }

            }

            writer.flush();
            response.setContentLength(writer.size());
            OutputStream out = response.getOutputStream();
            writer.writeTo(out);
            out.close();

            return;
        }
    }
}