/*
* 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$
* $Author: hannes $
* $Revision: 9942 $
* $Date: 2009-09-20 21:43:05 +0200 (Son, 20. Sep 2009) $
*/
package helma.main;
import helma.framework.core.*;
import helma.framework.repository.Repository;
import helma.framework.repository.FileRepository;
import helma.util.StringUtils;
import org.apache.xmlrpc.XmlRpcHandler;
import org.apache.commons.logging.Log;
import org.mortbay.jetty.handler.ContextHandler;
import org.mortbay.jetty.handler.ContextHandlerCollection;
import org.mortbay.jetty.handler.ResourceHandler;
import org.mortbay.jetty.servlet.ServletHandler;
import org.mortbay.jetty.servlet.ServletHolder;
import java.io.*;
import java.util.*;
import helma.util.ResourceProperties;
import helma.servlet.EmbeddedServletClient;
/**
* This class is responsible for starting and stopping Helma applications.
*/
public class ApplicationManager implements XmlRpcHandler {
private Hashtable descriptors;
private Hashtable applications;
private Hashtable xmlrpcHandlers;
private ResourceProperties props;
private Server server;
private long lastModified;
private ContextHandlerCollection context;
private JettyServer jetty = null;
/**
* Creates a new ApplicationManager object.
*
* @param props the properties defining the running apps
* @param server the server instance
*/
public ApplicationManager(ResourceProperties props, Server server) {
this.props = props;
this.server = server;
descriptors = new Hashtable();
applications = new Hashtable();
xmlrpcHandlers = new Hashtable();
lastModified = 0;
jetty = server.jetty;
}
/**
* Called regularely check applications property file
* to create and start new applications.
*/
protected void checkForChanges() {
if (props.lastModified() > lastModified && server.getApplicationsOption() == null) {
try {
for (Enumeration e = props.keys(); e.hasMoreElements();) {
String appName = (String) e.nextElement();
if ((appName.indexOf(".") == -1) &&
(applications.get(appName) == null)) {
AppDescriptor appDesc = new AppDescriptor(appName);
appDesc.start();
appDesc.bind();
}
}
// then stop deleted ones
for (Enumeration e = descriptors.elements(); e.hasMoreElements();) {
AppDescriptor appDesc = (AppDescriptor) e.nextElement();
// check if application has been removed and should be stopped
if (!props.containsKey(appDesc.appName)) {
appDesc.stop();
} else if (server.jetty != null) {
// If application continues to run, remount
// as the mounting options may have changed.
AppDescriptor ndesc = new AppDescriptor(appDesc.appName);
ndesc.app = appDesc.app;
appDesc.unbind();
ndesc.bind();
descriptors.put(ndesc.appName, ndesc);
}
}
} catch (Exception mx) {
getLogger().error("Error checking applications", mx);
}
lastModified = System.currentTimeMillis();
}
}
/**
* Start an application by name
*/
public void start(String appName) {
AppDescriptor desc = new AppDescriptor(appName);
desc.start();
}
/**
* Bind an application by name
*/
public void register(String appName) {
AppDescriptor desc = (AppDescriptor) descriptors.get(appName);
if (desc != null) {
desc.bind();
}
}
/**
* Stop an application by name
*/
public void stop(String appName) {
AppDescriptor desc = (AppDescriptor) descriptors.get(appName);
if (desc != null) {
desc.stop();
}
}
/**
* Start all applications listed in the properties
*/
public void startAll() {
try {
String[] apps = server.getApplicationsOption();
if (apps != null) {
for (int i = 0; i < apps.length; i++) {
AppDescriptor desc = new AppDescriptor(apps[i]);
desc.start();
}
} else {
for (Enumeration e = props.keys(); e.hasMoreElements();) {
String appName = (String) e.nextElement();
if (appName.indexOf(".") == -1) {
String appValue = props.getProperty(appName);
if (appValue != null && appValue.length() > 0) {
appName = appValue;
}
AppDescriptor desc = new AppDescriptor(appName);
desc.start();
}
}
}
for (Enumeration e = descriptors.elements(); e.hasMoreElements();) {
AppDescriptor appDesc = (AppDescriptor) e.nextElement();
appDesc.bind();
}
lastModified = System.currentTimeMillis();
} catch (Exception mx) {
getLogger().error("Error starting applications", mx);
mx.printStackTrace();
}
}
/**
* Stop all running applications.
*/
public void stopAll() {
for (Enumeration en = descriptors.elements(); en.hasMoreElements();) {
try {
AppDescriptor appDesc = (AppDescriptor) en.nextElement();
appDesc.stop();
} catch (Exception x) {
// ignore exception in application shutdown
}
}
}
/**
* Get an array containing all currently running applications.
*/
public Object[] getApplications() {
return applications.values().toArray();
}
/**
* Get an application by name.
*/
public Application getApplication(String name) {
return (Application) applications.get(name);
}
/**
* Implements org.apache.xmlrpc.XmlRpcHandler.execute()
*/
public Object execute(String method, Vector params)
throws Exception {
int dot = method.indexOf(".");
if (dot == -1) {
throw new Exception("Method name \"" + method +
"\" does not specify a handler application");
}
if ((dot == 0) || (dot == (method.length() - 1))) {
throw new Exception("\"" + method + "\" is not a valid XML-RPC method name");
}
String handler = method.substring(0, dot);
String method2 = method.substring(dot + 1);
Application app = (Application) xmlrpcHandlers.get(handler);
if (app == null) {
app = (Application) xmlrpcHandlers.get("*");
// use the original method name, the handler is resolved within the app.
method2 = method;
}
if (app == null) {
throw new Exception("Handler \"" + handler + "\" not found for " + method);
}
return app.executeXmlRpc(method2, params);
}
private String getMountpoint(String mountpoint) {
mountpoint = mountpoint.trim();
if ("".equals(mountpoint)) {
return "/";
} else if (!mountpoint.startsWith("/")) {
return "/" + mountpoint;
}
return mountpoint;
}
private String joinMountpoint(String prefix, String suffix) {
if (prefix.endsWith("/") || suffix.startsWith("/")) {
return prefix+suffix;
} else {
return prefix+"/"+suffix;
}
}
private String getPathPattern(String mountpoint) {
if (!mountpoint.startsWith("/")) {
mountpoint = "/"+mountpoint;
}
if ("/".equals(mountpoint)) {
return "/";
}
if (mountpoint.endsWith("/")) {
return mountpoint.substring(0, mountpoint.length()-1);
}
return mountpoint;
}
private File getAbsoluteFile(String path) {
// make sure our directory has an absolute path,
// see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4117557
File file = new File(path);
if (file.isAbsolute()) {
return file;
} else {
return file.getAbsoluteFile();
}
}
private Log getLogger() {
return server.getLogger();
}
private String findResource(String path) {
File file = new File(path);
if (!file.isAbsolute() && !file.exists()) {
file = new File(server.getHopHome(), path);
}
return file.getAbsolutePath();
}
/**
* Inner class that describes an application and its start settings.
*/
class AppDescriptor {
Application app;
private ContextHandler staticContext = null;
private ContextHandler appContext = null;
String appName;
File appDir;
File dbDir;
String mountpoint;
String pathPattern;
String staticDir;
String protectedStaticDir;
String staticMountpoint;
boolean staticIndex;
String[] staticHome;
String xmlrpcHandlerName;
String cookieDomain;
String sessionCookieName;
String protectedSessionCookie;
String uploadLimit;
String uploadSoftfail;
String debug;
Repository[] repositories;
String servletClassName;
/**
* extend apps.properties, add [appname].ignore
*/
String ignoreDirs;
/**
* Creates an AppDescriptor from the properties.
* @param name the application name
*/
AppDescriptor(String name) {
ResourceProperties conf = props.getSubProperties(name + '.');
appName = name;
mountpoint = getMountpoint(conf.getProperty("mountpoint", appName));
pathPattern = getPathPattern(mountpoint);
staticDir = conf.getProperty("static");
staticMountpoint = getPathPattern(conf.getProperty("staticMountpoint",
joinMountpoint(mountpoint, "static")));
staticIndex = "true".equalsIgnoreCase(conf.getProperty("staticIndex"));
String home = conf.getProperty("staticHome");
if (home == null) {
staticHome = new String[] {"index.html", "index.htm"};
} else {
staticHome = StringUtils.split(home, ",");
}
protectedStaticDir = conf.getProperty("protectedStatic");
cookieDomain = conf.getProperty("cookieDomain");
sessionCookieName = conf.getProperty("sessionCookieName");
protectedSessionCookie = conf.getProperty("protectedSessionCookie");
uploadLimit = conf.getProperty("uploadLimit");
uploadSoftfail = conf.getProperty("uploadSoftfail");
debug = conf.getProperty("debug");
String appDirName = conf.getProperty("appdir");
appDir = (appDirName == null) ? null : getAbsoluteFile(appDirName);
String dbDirName = conf.getProperty("dbdir");
dbDir = (dbDirName == null) ? null : getAbsoluteFile(dbDirName);
servletClassName = conf.getProperty("servletClass");
// got ignore dirs
ignoreDirs = conf.getProperty("ignore");
// read and configure app repositories
ArrayList repositoryList = new ArrayList();
Class[] parameters = { String.class };
for (int i = 0; true; i++) {
String repositoryArgs = conf.getProperty("repository." + i);
if (repositoryArgs != null) {
// lookup repository implementation
String repositoryImpl = conf.getProperty("repository." + i +
".implementation");
if (repositoryImpl == null) {
// implementation not set manually, have to guess it
if (repositoryArgs.endsWith(".zip")) {
repositoryArgs = findResource(repositoryArgs);
repositoryImpl = "helma.framework.repository.ZipRepository";
} else if (repositoryArgs.endsWith(".js")) {
repositoryArgs = findResource(repositoryArgs);
repositoryImpl = "helma.framework.repository.SingleFileRepository";
} else {
repositoryArgs = findResource(repositoryArgs);
repositoryImpl = "helma.framework.repository.FileRepository";
}
}
try {
Repository newRepository = (Repository) Class.forName(repositoryImpl)
.getConstructor(parameters)
.newInstance(new Object[] {repositoryArgs});
repositoryList.add(newRepository);
} catch (Exception ex) {
getLogger().error("Adding repository " + repositoryArgs + " failed. " +
"Will not use that repository. Check your initArgs!", ex);
}
} else {
// we always scan repositories 0-9, beyond that only if defined
if (i > 9) {
break;
}
}
}
if (appDir != null) {
FileRepository appRep = new FileRepository(appDir);
if (!repositoryList.contains(appRep)) {
repositoryList.add(appRep);
}
} else if (repositoryList.isEmpty()) {
repositoryList.add(new FileRepository(
new File(server.getAppsHome(), appName)));
}
repositories = new Repository[repositoryList.size()];
repositories = (Repository[]) repositoryList.toArray(repositories);
}
void start() {
getLogger().info("Building application " + appName);
try {
// create the application instance
app = new Application(appName, server, repositories, appDir, dbDir);
// register ourselves
descriptors.put(appName, this);
applications.put(appName, app);
// the application is started later in the register method, when it's bound
app.init(ignoreDirs);
// set application URL prefix if it isn't set in app.properties
if (!app.hasExplicitBaseURI()) {
app.setBaseURI(mountpoint);
}
app.start();
} catch (Exception x) {
getLogger().error("Error creating application " + appName, x);
x.printStackTrace();
}
}
void stop() {
getLogger().info("Stopping application " + appName);
// unbind application
unbind();
// stop application
try {
app.stop();
getLogger().info("Stopped application " + appName);
} catch (Exception x) {
getLogger().error("Couldn't stop app", x);
}
descriptors.remove(appName);
applications.remove(appName);
}
void bind() {
try {
getLogger().info("Binding application " + appName + " :: " + app.hashCode() + " :: " + this.hashCode());
// set application URL prefix if it isn't set in app.properties
if (!app.hasExplicitBaseURI()) {
app.setBaseURI(mountpoint);
}
// bind to Jetty HTTP server
if (jetty != null) {
if(context == null) {
context = new ContextHandlerCollection();
jetty.getHttpServer().setHandler(context);
}
// if there is a static direcory specified, mount it
if (staticDir != null) {
File staticContent = getAbsoluteFile(staticDir);
getLogger().info("Serving static from " + staticContent.getPath());
getLogger().info("Mounting static at " + staticMountpoint);
ResourceHandler rhandler = new ResourceHandler();
rhandler.setResourceBase(staticContent.getPath());
rhandler.setWelcomeFiles(staticHome);
staticContext = context.addContext(staticMountpoint, "");
staticContext.setHandler(rhandler);
staticContext.start();
}
appContext = context.addContext(pathPattern, "");
ServletHandler handler = new ServletHandler();
Class servletClass = servletClassName == null ?
EmbeddedServletClient.class : Class.forName(servletClassName);
ServletHolder holder = new ServletHolder(servletClass);
handler.addServletWithMapping(holder, "/*");
holder.setInitParameter("application", appName);
// holder.setInitParameter("mountpoint", mountpoint);
if (cookieDomain != null) {
holder.setInitParameter("cookieDomain", cookieDomain);
}
if (sessionCookieName != null) {
holder.setInitParameter("sessionCookieName", sessionCookieName);
}
if (protectedSessionCookie != null) {
holder.setInitParameter("protectedSessionCookie", protectedSessionCookie);
}
if (uploadLimit != null) {
holder.setInitParameter("uploadLimit", uploadLimit);
}
if (uploadSoftfail != null) {
holder.setInitParameter("uploadSoftfail", uploadSoftfail);
}
if (debug != null) {
holder.setInitParameter("debug", debug);
}
appContext.setHandler(handler);
if (protectedStaticDir != null) {
File protectedContent = getAbsoluteFile(protectedStaticDir);
appContext.setResourceBase(protectedContent.getPath());
getLogger().info("Serving protected static from " +
protectedContent.getPath());
}
// Remap the context paths and start
context.mapContexts();
appContext.start();
}
// register as XML-RPC handler
xmlrpcHandlerName = app.getXmlRpcHandlerName();
xmlrpcHandlers.put(xmlrpcHandlerName, app);
} catch (Exception x) {
getLogger().error("Couldn't bind app", x);
x.printStackTrace();
}
}
void unbind() {
getLogger().info("Unbinding application " + appName);
try {
// unbind from Jetty HTTP server
if (jetty != null) {
if (appContext != null) {
context.removeHandler(appContext);
appContext.stop();
appContext.destroy();
appContext = null;
}
if (staticContext != null) {
context.removeHandler(staticContext);
staticContext.stop();
staticContext.destroy();
staticContext = null;
}
context.mapContexts();
}
// unregister as XML-RPC handler
if (xmlrpcHandlerName != null) {
xmlrpcHandlers.remove(xmlrpcHandlerName);
}
} catch (Exception x) {
getLogger().error("Couldn't unbind app", x);
}
}
public String toString() {
return "[AppDescriptor "+app+"]";
}
}
}