blob: db89f9165468621c8ec15978b3d394b5a5de61a4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.oodt.commons;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.rmi.registry.Registry;
import java.rmi.server.RemoteObject;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.spi.NamingManager;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.oodt.commons.util.LogInit;
import org.apache.oodt.commons.util.XML;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* The MultiServer runs multiple server objects in a single JVM. Instead of running a
* separate product server, profile server, query server, and so forth, in their own JVMs,
* which are extremely heavy-weight operating system processes, we can put them into one
* JVM which reduces the memory footprint on a single computer enormously.
*
* <h3>Specifying the Configuration</h3>
*
* The MultiServer configuration is an XML document. Here's
* a sample:
* <pre>&lt;multiserver
* xmlns="http://oodt.jpl.nasa.gov/edm-commons/xml/multiserver"
* id="My Multi Server"&gt;
* &lt;server
* class="org.apache.oodt.commons.product.rmi.ProductServiceImpl"
* id="urn:eda:rmi:BioServer"
* bind="rebind" /&gt;
* &lt;server
* class="org.apache.oodt.commons.product.rmi.ProductServiceImpl"
* id="urn:eda:rmi:SpaceServer"
* bind="1800000" /&gt;
* &lt;server
* class="org.apache.oodt.commons.profile.rmi.ProfileServiceImpl"
* id="urn:eda:rmi:Resource"
* bind="bind" /&gt;
* &lt;properties&gt;
* &lt;property name="urn:eda:rmi:BioServer.handlers"&gt;
* edrn.MedHandler,edrn.SpecimenHandler
* &lt;/property&gt;
* &lt;property name="urn:eda:rmi:SpaceServer.handlers"&gt;
* pds.PlanetoidHandler
* &lt;/property&gt;
* &lt;property name="org.apache.oodt.commons.profile.Handlers"&gt;
* com.sun.ResourceHandler
* &lt;/property&gt;
* &lt;/properties&gt;
* &lt;/multiserver&gt;</pre>
*
* <p>This would start three servers (two products, one profile) with the various property
* settings indicated. The MultiServer expects the property
* <code>org.apache.oodt.commons.MultiServer.config</code> to identify the URL of the configuration. You
* can shorten that to <code>MultiServer.config</code> or <code>multiserver.config</code>
* or even just <code>config</code>, in that order. If none of those properties are
* specified then the MultiServer will expect the URL to be the first (and only) command
* line argument.
*
* <p>The <code>id</code> attribute on the <code>multiserver</code> element tells the name
* of the whole application; it's used to prefix log messages.
*
* <h3>Server Specification</h3>
*
* <p>Each <code>&lt;server&gt;</code> entry names a server to start. The
* <code>class</code> attribute is the name of the RMI-compatible Java class that the
* server will run. (Note that currently only RMI servers are supported.) The
* <code>id</code> attribute tells the name the server should use to register with the
* naming context. And the <code>bind</code> attribute tells how the registration should
* proceed. It can take on the following values:
*
* <ul>
* <li><code>true</code> meaning the object will attempt a bind. If the ID is already
* bound in the context, the MultiServer fails.</li>
*
* <li><code>false</code> meaning the object won't be bound.</li>
*
* <li><code>rebind</code> meaning the object will rebind its ID in the context,
* overwriting any previous binding.
*
* <li><var>number</var> meaining the object will rebind its ID once at starup, and
* every <var>number</var> milliseconds thereafter. This is, in OODT's experience, the
* most useful option as it helps an entire dpeloyed network of servers self-heal after
* a naming context restart.
* </ul>
*
* <h3>Propery Specification</h3>
*
* <p>For convenience, System Properties may be specified in the configirutaion as well.
* However, any properties already defined (such as using the <code>-D</code> command-line
* argument) get priority and their values won't be overridden. To specify properties,
* list any number of <code>&lt;property&gt;</code> elements under the
* <code>&lt;properties&gt;</code> element with a <code>name</code> attribute naming the
* System Property key and the text of the element naming its value. Note that the text
* will be unwrapped.
*
* @author Kelly
* @version $Revision: 1.3 $
*/
public class MultiServer {
/**
* Start the multi server.
*
* @param argv Command-line arguments.
* @throws Throwable if an error occurs.
*/
public static void main(String[] argv) throws Throwable {
String config = System.getProperty("org.apache.oodt.commons.MultiServer.config", System.getProperty("MultiServer.config",
System.getProperty("multiserver.config", System.getProperty("config"))));
if (config == null) {
if (argv.length != 1)
throw new IllegalStateException("No org.apache.oodt.commons.MultiServer.config property or config URL argument");
else
config = argv[0];
}
StringReader reader = new StringReader(CONFIG);
Configuration.configuration = new Configuration(new InputSource(reader));
reader.close();
parseConfig(new InputSource(config));
Hashtable t = new Hashtable();
t.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.oodt.commons.object.jndi.ObjectCtxFactory");
String registryList = System.getProperty("org.apache.oodt.commons.rmiregistries", System.getProperty("rmiregistries"));
if (registryList == null) {
String host = System.getProperty("rmiregistry.host", "localhost");
int port = Integer.getInteger("rmiregistry.port", Registry.REGISTRY_PORT).intValue();
registryList = "rmi://" + host + ":" + port;
}
t.put("rmiregistries", registryList);
context = NamingManager.getInitialContext(t);
ExecServer.runInitializers();
try {
LogInit.init(System.getProperties(), getAppName());
if (servers.isEmpty()) throw new IllegalStateException("No servers defined in config");
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
shutdown();
}
});
startup();
} catch (Throwable ex) {
ex.printStackTrace();
try {
shutdown();
} catch (Throwable ignore) {}
System.exit(1);
}
for (;;) try {
Thread.currentThread().join();
} catch (InterruptedException ignore) {}
}
/**
* Parse the MultiServer configuration.
*
* @param is Source of the configuration.
* @throws ParserConfigurationException If we can't create a parser.
* @throws SAXException If there's a parse error.
* @throws IOException If there's a problem reading the configuration.
* @throws ClassNotFoundException If we can't find a server class.
* @throws NoSuchMethodException If the server class doesn't have the right constructor.
* @throws InstantiationException If we can create a server object.
* @throws IllegalAccessException If the server constructor isn't accessible.
* @throws InvocationTargetException If the server constructor throws an exception.
*/
static void parseConfig(InputSource is) throws ParserConfigurationException, SAXException, IOException,
ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException,
InvocationTargetException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);
factory.setNamespaceAware(true);
factory.setValidating(false);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(is);
Element root = doc.getDocumentElement();
appName = root.getAttribute("id");
if (appName == null) throw new SAXException("id attribute missing from multiserver element");
// Set properties
NodeList children = root.getChildNodes();
for (int i = 0; i < children.getLength(); ++i) {
Node node = (Node) children.item(i);
if ("properties".equals(node.getNodeName())) {
NodeList props = ((Element) node).getElementsByTagName("property");
for (int j = 0; j < props.getLength(); ++j) {
Element property = (Element) props.item(j);
String name = property.getAttribute("name");
if (!System.getProperties().containsKey(name)) {
String value = XML.unwrappedText(property);
System.setProperty(name, value);
}
}
}
}
// Create servers
NodeList serverNodes = root.getElementsByTagName("server");
servers = new HashMap();
for (int i = 0; i < serverNodes.getLength(); ++i) {
Element serverElem = (Element) serverNodes.item(i);
String name = serverElem.getAttribute("id");
if (name == null) throw new SAXException("id attribute missing from server element");
String className = serverElem.getAttribute("class");
if (className == null) throw new SAXException("class attribute missing from server element");
String bindKind = serverElem.getAttribute("bind");
if (bindKind == null) throw new SAXException("bind attribute missing from server element");
Server server;
if ("true".equals(bindKind))
server = new BindingServer(name, className);
else if ("false".equals(bindKind))
server = new NonbindingServer(name, className);
else if ("rebind".equals(bindKind))
server = new RebindingServer(name, className);
else try {
long period = Long.parseLong(bindKind);
server = new AutobindingServer(name, className, period);
} catch (NumberFormatException ex) {
throw new SAXException("Expected true, false, rebind, or auto for bind attribute but got `"
+ bindKind + "'");
}
servers.put(name, server);
}
}
/**
* Start each server.
*
* @throws NamingException if an error occurs.
*/
static void startup() throws NamingException {
for (Iterator i = servers.values().iterator(); i.hasNext();) {
Server s = (Server) i.next();
s.start();
}
}
/**
* Stop each server.
*/
static void shutdown() {
for (Iterator i = servers.values().iterator(); i.hasNext();) try {
Server s = (Server) i.next();
s.stop();
} catch (NamingException ignore) {}
TIMER.cancel();
}
/**
* Get the name of the application.
*
* @return a {@link String} value.
*/
static String getAppName() {
return appName;
}
/**
* Get the servers. Keys are {@String} names and values are {@link #Server}s.
*
* @return a {@link Map} of the defined servers.
*/
static Map getServers() {
return servers;
}
/**
* A server.
*/
static abstract class Server extends ExecServer {
/**
* Creates a new {@link Server} instance.
*
* @param name ID under which to register.
* @param className Class of server to instantiate.
* @throws ClassNotFoundException If we can't find the server class.
* @throws NoSuchMethodException If the server class doesn't have the right constructor.
* @throws InstantiationException If we can create the server object.
* @throws IllegalAccessException If the server constructor isn't accessible.
* @throws InvocationTargetException If the server constructor throws an exception.
*/
protected Server(String name, String className) throws ClassNotFoundException, NoSuchMethodException,
InstantiationException, IllegalAccessException, InvocationTargetException {
super(name);
this.className = className;
Class clazz = Class.forName(className);
Constructor ctor = clazz.getConstructor(new Class[]{ ExecServer.class });
servant = (RemoteObject) ctor.newInstance(new Object[]{ this });
}
/**
* Get the name of the server class to instantiate.
*
* @return a {@link String} value.
*/
public String getClassName() {
return className;
}
/**
* Get the type of binding this server will perform. Possible values are
* {@link #BINDING}, {@link #NONBINDING}, {@link #REBINDING}, or {@link
* #AUTO}.
*
* @return An integer identifiying the binding behavior.
*/
public abstract int getBindingBehavior();
/**
* Start this server.
*
* @throws NamingException if an error occurs during the binding.
*/
public abstract void start() throws NamingException;
/**
* Stop this server.
*
* @throws NamingException if an error occurs during unbinding.
*/
public abstract void stop() throws NamingException;
/** Name of server class. */
protected String className;
/** Server object. */
protected RemoteObject servant;
}
/** Inidcates server will try a bind. */
public static final int BINDING = 1;
/** Indicates server won't be bound. */
public static final int NONBINDING = 2;
/** Indicates server will try a rebind. */
public static final int REBINDING = 3;
/** Indicates server will try periodic rebinding. */
public static final int AUTO = 4;
/** Timer to schedule periodic rebinind. */
private static final Timer TIMER = new Timer(/*isDaemon*/true);
/** Name of this application. */
private static String appName;
/** Known servers. Keys are {@String} names and values are {@link #Server}s. */
private static Map servers;
/** The naming context. */
private static Context context;
/** The edarc.xml file to satisfy the MultiServer's servers. */
private static final String CONFIG = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE configuration PUBLIC"
+ " \"-//JPL//DTD EDA Configuration 1.0//EN\" \"http://enterprise.jpl.nasa.gov/dtd/configuration.dtd\">\n"
+ "<configuration><webServer><host>localhost</host><port>80</port></webServer>"
+ "<nameServer><iiop><host>localhost</host><port>10000</port></iiop></nameServer></configuration>";
/**
* A server that tries a single bind.
*/
static class BindingServer extends Server {
/**
* Creates a new {@link BindingServer} instance.
*
* @param name ID under which to register.
* @param className Class of server to instantiate.
* @throws ClassNotFoundException If we can't find the server class.
* @throws NoSuchMethodException If the server class doesn't have the right constructor.
* @throws InstantiationException If we can create the server object.
* @throws IllegalAccessException If the server constructor isn't accessible.
* @throws InvocationTargetException If the server constructor throws an exception.
*/
BindingServer(String name, String className) throws ClassNotFoundException, NoSuchMethodException,
InstantiationException, IllegalAccessException, InvocationTargetException {
super(name, className);
}
public int getBindingBehavior() {
return BINDING;
}
/**
* Start by binding.
*
* @throws NamingException if an error occurs.
*/
public void start() throws NamingException {
context.bind(name, servant);
}
/**
* Stop by unbinding.
*
* @throws NamingException if an error occurs.
*/
public void stop() throws NamingException {
context.unbind(name);
}
}
/**
* A (named, but anonymous) server that isn't bound to a naming context.
*/
static class NonbindingServer extends Server {
/**
* Creates a new {@link NonbindingServer} instance.
*
* @param name ID under which to register.
* @param className Class of server to instantiate.
* @throws ClassNotFoundException If we can't find the server class.
* @throws NoSuchMethodException If the server class doesn't have the right constructor.
* @throws InstantiationException If we can create the server object.
* @throws IllegalAccessException If the server constructor isn't accessible.
* @throws InvocationTargetException If the server constructor throws an exception.
*/
NonbindingServer(String name, String className) throws ClassNotFoundException, NoSuchMethodException,
InstantiationException, IllegalAccessException, InvocationTargetException {
super(name, className);
}
public int getBindingBehavior() {
return NONBINDING;
}
/**
* Start by taking no action.
*/
public void start() {}
/**
* Stop by taking no action.
*/
public void stop() {}
}
/**
* A server that rebinds its ID in the naming context.
*/
static class RebindingServer extends BindingServer {
/**
* Creates a new {@link RebindingServer} instance.
*
* @param name ID under which to register.
* @param className Class of server to instantiate.
* @throws ClassNotFoundException If we can't find the server class.
* @throws NoSuchMethodException If the server class doesn't have the right constructor.
* @throws InstantiationException If we can create the server object.
* @throws IllegalAccessException If the server constructor isn't accessible.
* @throws InvocationTargetException If the server constructor throws an exception.
*/
RebindingServer(String name, String className) throws ClassNotFoundException, NoSuchMethodException,
InstantiationException, IllegalAccessException, InvocationTargetException {
super(name, className);
}
public int getBindingBehavior() {
return REBINDING;
}
/**
* Start by rebinding in naming context.
*
* @throws NamingException if an error occurs.
*/
public void start() throws NamingException {
context.rebind(name, servant);
}
}
/**
* A server that periodically rebinds its ID in the naming context.
*/
static class AutobindingServer extends Server {
/**
* Creates a new {@link AutobindingServer} instance.
*
* @param name ID under which to register.
* @param className Class of server to instantiate.
* @throws ClassNotFoundException If we can't find the server class.
* @throws NoSuchMethodException If the server class doesn't have the right constructor.
* @throws InstantiationException If we can create the server object.
* @throws IllegalAccessException If the server constructor isn't accessible.
* @throws InvocationTargetException If the server constructor throws an exception.
*/
AutobindingServer(String name, String className, long period) throws ClassNotFoundException, NoSuchMethodException,
InstantiationException, IllegalAccessException, InvocationTargetException {
super(name, className);
this.period = period;
}
public int getBindingBehavior() {
return AUTO;
}
/**
* Start by scheduling the timer task that rebinds this server.
*/
public void start() {
TIMER.schedule(binder = new Binder(), /*delay*/0L, /*period*/period);
}
/**
* Stop by canceling the timer task and unbinding from the server.
*
* @throws NamingException if an error occurs.
*/
public void stop() throws NamingException {
if (binder != null) binder.cancel();
context.unbind(name);
}
/**
* Get how often this server process will be rebound.
*
* @return Period in milliseconds.
*/
public long getPeriod() {
return period;
}
/** How often to rebind in milliseconds. */
private long period;
/** Timer task that rebinds. */
private Binder binder;
/**
* Timer task that rebinds this server to the naming context.
*/
private class Binder extends TimerTask {
public void run() {
try {
context.rebind(name, servant);
} catch (NamingException ex) {}
}
}
}
}