/* * The contents of this file are subject to the Mozilla Public License * Version 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is the Kowari Metadata Store. * * The Initial Developer of the Original Code is Plugged In Software Pty * Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002 * Plugged In Software Pty Ltd. All Rights Reserved. * * Contributor(s): N/A. * * [NOTE: The text of this Exhibit A may differ slightly from the text * of the notices in the Source Code files of the Original Code. You * should use the text of this Exhibit A rather than the text found in the * Original Code Source Code for Your Modifications.] * */ package org.mulgara.server; // Java 2 standard packages import java.io.*; import java.lang.reflect.*; import java.net.*; // Third party packages /* import javax.jmdns.*; // ZeroConf (multicast DNS) */ import org.apache.log4j.*; // Apache Log4J // Locally written packages import org.mulgara.config.MulgaraConfig; import org.mulgara.query.*; import org.mulgara.server.SessionFactory; import org.mulgara.util.TempDir; /** * JMX manageable {@link SessionFactory} and transport wrapper. Concrete * subclasses are expected to use the {@link #getSessionFactory} method to * obtain a reference to the local database, and implement the abstract {@link * #startService} and {@link #stopService} methods to start and stop a network * service for the database. A separate proxy {@link SessionFactory} * implementation must be created to use this network service to provide remote * access to the database. * * @created 2001-09-15 * @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a> * @copyright ©2001 <a href="http://www.pisoftware.com/">Plugged In Software Pty Ltd</a> * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a> * @see <a href="http://developer.java.sun.com/developer/JDCTechTips/2001/tt0327.html#jndi"> * <cite>JNDI lookup in distributed systems</cite> </a> */ public abstract class AbstractServer implements ServerMBean { /** Logger. This is named after the classname. */ private final static Logger logger = Logger.getLogger(AbstractServer.class.getName()); /** The class name of the {@link SessionFactory} implementation. */ private String providerClassName; /** The directory where the server can write files. */ private File dir; /** The directory where the server can write temporary files. */ private File tempdir; /** The server URI. */ private URI uri; /** * The port number to bind to. */ private int portNumber = -1; /** The hostname of the server. */ protected String hostname = null; /** State. */ private ServerState state = ServerState.UNINITIALIZED; /** The server object. */ private SessionFactory sessionFactory; /** The session factory config object */ private MulgaraConfig sessionConfig; // /** ZeroConf server. */ // private JmDNS jmdns; /** * Returns the hostname of the server. * @return The Hostname value */ public String getHostname() { return hostname; } /** * Sets the server port. * @param newPortNumber The new port to bind to. */ public void setPortNumber(int newPortNumber) { // Prevent the port from being changed while the server is up if (getState() == ServerState.STARTED) { throw new IllegalStateException("Can't change server port without first stopping the server"); } portNumber = newPortNumber; if (uri != null && uri.getPort() == -1) uri = updateUriPort(uri); // else expect URI to be updated now } /** * Returns the port number that the server is to bind to. * @return the port number value. */ public int getPortNumber() { return portNumber; } /** * Sets the hostname of the server. * @param newHostname the hostname of the server, if <code>null</code> <code>localhost</code> will be used * @throws IllegalStateException if the service is STARTED or if the underlying session factory already has a fixed hostname */ public void setHostname(String newHostname) { // Prevent the hostname from being changed while the server is up if (this.getState() == ServerState.STARTED) { throw new IllegalStateException("Can't change hostname without first stopping the server"); } // Reset the field if (newHostname == null) { this.hostname = "localhost"; logger.warn("Hostname supplied is null, defaulting to localhost"); } else { hostname = newHostname; } } /** * Set the database directory. * * @param dir The new Dir value */ public void setDir(File dir) { this.dir = dir; } /** * Set the temporary database directory. * @param tempdir The new Dir value */ public void setTempDir(File tempdir) { this.tempdir = tempdir; } /** * Set the database provider classname. * @param providerClassName The new ProviderClassName value */ public void setProviderClassName(String providerClassName) { this.providerClassName = providerClassName; } // // MBean properties // /** * Read the server state. * @return The current server state. */ public ServerState getState() { return state; } /** * Read the database directory. * @return The Dir value */ public File getDir() { return dir; } /** * Read the temporary database directory. * @return The tempdir value */ public File getTempDir() { return tempdir; } /** * Read the database provider classname. * @return The ProviderClassName value */ public String getProviderClassName() { return providerClassName; } /** * Read the database URI. * @return The URI value */ public URI getURI() { return uri; } /** * Allow access by subclasses to the session factory. * @return <code>null</code> in the * {@link org.mulgara.server.ServerMBean.ServerState#UNINITIALIZED} state, * the {@link SessionFactory} instance otherwise */ public SessionFactory getSessionFactory() { return sessionFactory; } // // MBean actions // /** * Initialize the server. This involves creating the persistence files for the * database. Initialization requires a non-null <var>name</var> and <var> * providerClassName</var> . * * @throws ClassNotFoundException if the <var>providerClassName</var> isn't in the classpath * @throws IOException if the persistence directory doesn't exist and can't be created * @throws NoSuchMethodException if the <var>providerClassName</var> doesn't have a * constructor with the correct signature * @throws IllegalAccessException There was a permissions error during setup. * @throws InstantiationException The {@link SessionFactory} could not be created. * @throws InvocationTargetException The {@link SessionFactory} had an error during construction. */ public final void init() throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, IOException, NoSuchMethodException { if (logger.isInfoEnabled()) logger.info("Create server"); // Validate state if (dir == null) throw new IllegalStateException("Must set \"dir\" property"); if (tempdir == null) throw new IllegalStateException("Must set \"tempdir\" property"); if (providerClassName == null) throw new IllegalStateException("Must set \"providerClassName\" property"); // Create the directory if necessary if (!dir.isDirectory() && !dir.mkdir()) { logger.fatal("Unable to create database directory: " + dir); throw new Error("Fatal: Unable to create database directory: " + dir); } // create the temporary directory if (!tempdir.isDirectory() && !tempdir.mkdirs()) { File newTmp = TempDir.getTempDir(); logger.warn("Unable to create temporary directory: '" + tempdir + "', using: " + newTmp); tempdir = newTmp; } // Log provider class name if (logger.isInfoEnabled()) logger.info("Provider class is " + providerClassName); if (sessionConfig == null) { // Create the session factory using the two parameter constructor if we // have no configuration sessionFactory = (SessionFactory)Class.forName(providerClassName) .getConstructor(new Class[] {URI.class, File.class}) .newInstance(new Object[] {getURI(), dir}); } else { // Create the session factory using the three parameter constructor if we // have a configuration to use sessionFactory = (SessionFactory)Class.forName(providerClassName) .getConstructor(new Class[] {URI.class, File.class, MulgaraConfig.class}) .newInstance(new Object[] {getURI(), dir, sessionConfig}); } state = ServerState.STOPPED; // Log successful creation if (logger.isInfoEnabled()) logger.info("Created server"); } /** * Make the server available over the network. If successful the new state * should be {@link org.mulgara.server.ServerMBean.ServerState#STARTED}. * @throws IllegalStateException The service is not configured, or is already running. * @throws Exception A service specific error occurred during startup. */ public final void start() throws Exception { logger.info("Starting"); // Validate state if (state == ServerState.UNINITIALIZED) throw new IllegalStateException("Not initialized"); if (state == ServerState.STARTED) throw new IllegalStateException("Already STARTED"); assert state == ServerState.STOPPED; // Invariant tests for the STOPPED state if (sessionFactory == null) throw new AssertionError("Null \"sessionFactory\" parameter"); startService(); // Start advertising the service via ZeroConf // if (jmdns == null) { // try { // logger.info("Starting ZeroConf server"); // jmdns = new JmDNS(InetAddress.getLocalHost()); // logger.info("Started ZeroConf server"); // String type = "_rmi._tcp.local"; // logger.info("Registering as itql."+type+" on port 1099 with ZeroConf server"); // jmdns.registerService(new ServiceInfo( // type, // type // "itql."+type, // name // 1099, // port // 0, // weight // 0, // priority // "path=server1" // text // )); // logger.info("Registered with ZeroConf server"); // } catch (IOException e) { // logger.warn("Couldn't start ZeroConf server", e); // } // } state = ServerState.STARTED; logger.info("Started"); } /** * Make the server unavailable over the network. If successful the new state * will be {@link org.mulgara.server.ServerMBean.ServerState#STOPPED}. * @throws IllegalStateException If the server is not running * @throws Exception There was a service specific error in shutting down. */ public final void stop() throws Exception { logger.info("Shutting down"); // Validate state if (state != ServerState.STARTED) throw new IllegalStateException("Server is not STARTED"); // if (jmdns != null) { // logger.info("Unregistering from ZeroConf server"); // jmdns.unregisterAllServices(); // } stopService(); state = ServerState.STOPPED; logger.info("Shut down"); } /** * Destroy the server. */ public final void destroy() { logger.info("Destroying"); try { sessionFactory.close(); } catch (QueryException e) { logger.warn("Couldn't close server " + uri, e); } // if (jmdns != null) { // jmdns.unregisterAllServices(); // } sessionFactory = null; state = ServerState.UNINITIALIZED; logger.info("Destroyed"); } /** * Set the database URI. Implementing subclasses would typically derive a URI * from other properties and call this method whenever those properties are * modified. * @param uri the desired server URI, or <code>null</code> * @throws IllegalStateException if the service is STARTED or if the * underlying session factory already has a fixed URI */ protected void setURI(URI uri) { // Prevent the URI from being changed while the server is up if (state == ServerState.STARTED) throw new IllegalStateException("Can't change URI without first stopping the server"); this.uri = uri; } /** * Retrieves the configuration used when initialising a session factory. * @return The configuration used when initialising a session factory */ public MulgaraConfig getConfig() { return sessionConfig; } /** * Sets the configuration to be used when bringing up a session factory. * @param config The configuration to be used when bringing up a sessionfactory */ public void setConfig(MulgaraConfig config) { // Store the new configuration item sessionConfig = config; } /** * Utility to check that the URI has the port set correctly. * If the port is not specified in the URI and this server instance has a non-default port, * then the URI is updated to the new port with a warning. If the port is specified and it does * not match the server port, then an IllegalArgumentException is thrown. * @param u The port to check. * @return IllegalArgumentException If the port specified in the URI is different to the * port in use by this server. */ protected URI updateUriPort(URI u) { int port = u.getPort(); if (port == -1) { if (portNumber != getDefaultPort()) { try { u = new URI(u.getScheme(), u.getUserInfo(), u.getHost(), portNumber, u.getPath(), u.getQuery(), u.getFragment()); } catch (URISyntaxException e) { throw new IllegalArgumentException("URI has unexpected structure: " + u); } logger.warn("Setting server URI without configured port. Changing to: "); } } else { if (port != portNumber) throw new IllegalArgumentException("Server URI must match the port <" + uri +"> port:" + portNumber); } return u; } /** * Indicates the default port used by this server's protocol. This is a static final value * and should correspond to the scheme in the URI (e.g. http => port 80). * @return The default port for this server. */ protected abstract int getDefaultPort(); /** * Start the service that this server provides. * @throws Exception Indicating an error in server startup. */ protected abstract void startService() throws Exception; /** * Stop the service that this server provides. * @throws Exception Indicating an error in server shutdown. */ protected abstract void stopService() throws Exception; }