/* | |
* 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><multiserver | |
* xmlns="http://oodt.jpl.nasa.gov/edm-commons/xml/multiserver" | |
* id="My Multi Server"> | |
* <server | |
* class="org.apache.oodt.commons.product.rmi.ProductServiceImpl" | |
* id="urn:eda:rmi:BioServer" | |
* bind="rebind" /> | |
* <server | |
* class="org.apache.oodt.commons.product.rmi.ProductServiceImpl" | |
* id="urn:eda:rmi:SpaceServer" | |
* bind="1800000" /> | |
* <server | |
* class="org.apache.oodt.commons.profile.rmi.ProfileServiceImpl" | |
* id="urn:eda:rmi:Resource" | |
* bind="bind" /> | |
* <properties> | |
* <property name="urn:eda:rmi:BioServer.handlers"> | |
* edrn.MedHandler,edrn.SpecimenHandler | |
* </property> | |
* <property name="urn:eda:rmi:SpaceServer.handlers"> | |
* pds.PlanetoidHandler | |
* </property> | |
* <property name="org.apache.oodt.commons.profile.Handlers"> | |
* com.sun.ResourceHandler | |
* </property> | |
* </properties> | |
* </multiserver></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><server></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><property></code> elements under the | |
* <code><properties></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) {} | |
} | |
} | |
} | |
} |