Added: incubator/oodt/trunk/commons/src/main/java/org/apache/oodt/commons/MultiServer.java URL: http://svn.apache.org/viewvc/incubator/oodt/trunk/commons/src/main/java/org/apache/oodt/commons/MultiServer.java?rev=964248&view=auto ============================================================================== --- incubator/oodt/trunk/commons/src/main/java/org/apache/oodt/commons/MultiServer.java (added) +++ incubator/oodt/trunk/commons/src/main/java/org/apache/oodt/commons/MultiServer.java Wed Jul 14 23:02:32 2010 @@ -0,0 +1,576 @@ +/* + * 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) {} + } + } + } +}
Added: incubator/oodt/trunk/commons/src/main/java/org/apache/oodt/commons/Service.java URL: http://svn.apache.org/viewvc/incubator/oodt/trunk/commons/src/main/java/org/apache/oodt/commons/Service.java?rev=964248&view=auto ============================================================================== --- incubator/oodt/trunk/commons/src/main/java/org/apache/oodt/commons/Service.java (added) +++ incubator/oodt/trunk/commons/src/main/java/org/apache/oodt/commons/Service.java Wed Jul 14 23:02:32 2010 @@ -0,0 +1,61 @@ +/* + * 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.rmi.Remote; +import java.rmi.RemoteException; + +/** + * An enterprise service. + * + * @author Kelly + * @version $Revision: 1.1 $ + */ +public interface Service extends Remote { + /** + * Get the interface name of the service. + * + * Nominally, this should return the fully qualified class name of the + * implementation class, not the interface name. No idea how that "standard" got + * started. So, a good return value might be + * <code>jpl.oodt.product.rmi.ProductServiceImpl</code> and <strong>not</strong> + * <code>jpl.oodt.product.ProductService</code>. + * + * @return a <code>String</code> value. + * @throws RemoteException if an error occurs. + */ + String getServerInterfaceName() throws RemoteException; + + /** + * Control the server. + * + * @param command a <code>byte[]</code> value. + * @return Response. + * @throws RemoteException if an error occurs. + */ + byte[] control(byte[] command) throws RemoteException; + + + /** + * Control the server asynchronously. + * + * @param command a <code>byte[]</code> value. + * @throws RemoteException if an error occurs. + */ + void controlAsync(byte[] command) throws RemoteException; +}
