[Mulgara-svn] r1251 - in trunk/src: jar/server/java/org/mulgara/server war/descriptor
pag at mulgara.org
pag at mulgara.org
Sat Sep 6 11:51:51 UTC 2008
Author: pag
Date: 2008-09-06 04:51:50 -0700 (Sat, 06 Sep 2008)
New Revision: 1251
Added:
trunk/src/jar/server/java/org/mulgara/server/ExceptionList.java
trunk/src/jar/server/java/org/mulgara/server/HttpServices.java
Modified:
trunk/src/jar/server/java/org/mulgara/server/EmbeddedMulgaraServer.java
trunk/src/jar/server/java/org/mulgara/server/ServletMulgaraServer.java
trunk/src/war/descriptor/index.jsp
Log:
Isolated Http loading into it's own classes
Modified: trunk/src/jar/server/java/org/mulgara/server/EmbeddedMulgaraServer.java
===================================================================
--- trunk/src/jar/server/java/org/mulgara/server/EmbeddedMulgaraServer.java 2008-09-06 11:50:30 UTC (rev 1250)
+++ trunk/src/jar/server/java/org/mulgara/server/EmbeddedMulgaraServer.java 2008-09-06 11:51:50 UTC (rev 1251)
@@ -37,7 +37,6 @@
import java.util.*;
import javax.naming.*;
-import javax.servlet.Servlet;
import javax.xml.parsers.*;
import org.xml.sax.SAXException;
@@ -51,24 +50,10 @@
import org.mulgara.server.SessionFactory;
import org.mulgara.store.StoreException;
import org.mulgara.store.xa.SimpleXAResourceException;
-import org.mulgara.util.MortbayLogger;
-import org.mulgara.util.Reflect;
import org.mulgara.util.TempDir;
import static org.mulgara.server.ServerMBean.ServerState;
-import static org.mortbay.jetty.servlet.Context.SESSIONS;
-// jetty packages
-import org.mortbay.jetty.AbstractConnector;
-import org.mortbay.jetty.Handler;
-import org.mortbay.jetty.Server;
-import org.mortbay.jetty.handler.ContextHandler;
-import org.mortbay.jetty.nio.BlockingChannelConnector;
-import org.mortbay.jetty.servlet.ServletHolder;
-import org.mortbay.jetty.webapp.WebAppClassLoader;
-import org.mortbay.jetty.webapp.WebAppContext;
-import org.mortbay.util.MultiException;
-
/**
* Embedded production Mulgara server.
*
@@ -102,24 +87,6 @@
/** The request required to shutdown mulgara. */
public final static String SHUTDOWN_MSG = "shutdownmulgara";
- /** The key to the bound host name in the attribute map of the servlet context. */
- public final static String BOUND_HOST_NAME_KEY = "boundHostname";
-
- /** Key to the bound server model uri in the attribute map of the servlet context. */
- public final static String SERVER_MODEL_URI_KEY = "serverModelURI";
-
- /** The web application file path. */
- private final static String WEBAPP_PATH = "webapps";
-
- /** The Web Services web application file. */
- private final static String WEBSERVICES_WEBAPP = "webservices.war";
-
- /** The Web Services path. */
- private final static String WEBSERVICES_PATH = "webservices";
-
- /** The Web Query path. */
- private final static String WEBQUERY_PATH = "webui";
-
/** The logging category to log to. */
protected static Logger log = Logger.getLogger(EmbeddedMulgaraServer.class.getName());
@@ -147,14 +114,11 @@
/** The system property to disable the RMI service. */
private static final String DISABLE_RMI = "no_rmi";
- /** The maximum number of acceptors that Jetty can handle. It locks above this number. */
- private static final int WEIRD_JETTY_THREAD_LIMIT = 24;
-
/** The Mulgara server instance. In this case, an RMIServer. */
private ServerMBean serverManagement = null;
- /** The HTTP server instance. */
- private Server httpServer = null;
+ /** The web service container. */
+ private HttpServices webServices = null;
/** The embedded Mulgara server configuration */
private MulgaraConfig mulgaraConfig = null;
@@ -171,6 +135,9 @@
/** A flag to indicate if the server is configured to be started. */
private boolean canStart = false;
+ /** A flag to indicate if the http server is configured to be started. */
+ private boolean httpEnabled;
+
/**
* Starts a Mulgara server and a WebServices (SOAP) server to handle SOAP queries to the
* Mulgara server.
@@ -219,9 +186,9 @@
shutdownServer.start();
}
- } catch (MultiException me) {
- for (Throwable e: (List<Throwable>)me.getThrowables()) {
- log.error("MultiException", e);
+ } catch (ExceptionList el) {
+ for (Throwable e: (List<Throwable>)el.getCauses()) {
+ log.error("ExceptionList", e);
e.printStackTrace();
}
System.exit(2);
@@ -404,10 +371,10 @@
Runtime.getRuntime().addShutdownHook(new RuntimeShutdownHook(this));
// create a web service
- if (System.getProperty(DISABLE_HTTP) == null && !mulgaraConfig.getJetty().getDisabled()) {
+ if (httpEnabled) {
// create a HTTP server instance
if (log.isDebugEnabled()) log.debug("Configuring HTTP server");
- httpServer = createHttpServer();
+ webServices = new HttpServices(this, httpHostName, mulgaraConfig);
}
}
}
@@ -418,13 +385,13 @@
* @throws IllegalStateException if this method is called before the servers have been created
* @throws IOException if the Mulgara server cannot access its state keeping files
* @throws NamingException if the Mulgara server cannot communicate with the RMI registry
- * @throws MultiException if an error ocurrs while starting up the SOAP server
+ * @throws ExceptionList if an error ocurrs while starting up the SOAP server
* @throws SimpleXAResourceException If operations on the database cannot be instigated
* @throws Exception General catch all exception
* @throws StoreException if the database could not be started
*/
public void startServices() throws IOException, NamingException,
- MultiException, SimpleXAResourceException, StoreException, Exception {
+ ExceptionList, SimpleXAResourceException, StoreException, Exception {
if (serverManagement == null) throw new IllegalStateException("Servers must be created before they can be started");
@@ -439,9 +406,9 @@
ServerInfo.setLocalSessionFactory(((AbstractServer)serverManagement).getSessionFactory());
// start the HTTP server if required
- if (httpServer != null) {
+ if (webServices != null) {
if (log.isDebugEnabled()) log.debug("Starting HTTP server");
- httpServer.start();
+ webServices.start();
}
}
@@ -471,7 +438,7 @@
* Returns the Mulgara server instance.
* @return the Mulgara server instance
*/
- private ServerMBean getServerMBean() {
+ ServerMBean getServerMBean() {
return serverManagement;
}
@@ -489,7 +456,7 @@
* Returns the (RMI) name of the server.
* @return the (RMI) name of the server
*/
- private String getServerName() {
+ String getServerName() {
return rmiServerName;
}
@@ -504,25 +471,6 @@
/**
- * Returns the hostname to accept HTTP requests on.
- *
- * @return the hostname to accept HTTP requests on
- */
- private String getHttpHostName() {
- return this.httpHostName;
- }
-
-
- /**
- * Returns the SOAP server instance.
- * @return the SOAP server instance
- */
- private Server getHttpServer() {
- return httpServer;
- }
-
-
- /**
* Configures an embedded Mulgara server.
* @param parser the options parser containing the command line arguments to the server
* @return true if the server is allowed to start
@@ -603,17 +551,23 @@
loadLoggingConfig(mulgaraConfig.getExternalConfigPaths().getMulgaraLogging());
}
- Connector httpConnector = mulgaraConfig.getJetty().getConnector();
-
- String httpHost = (String)parser.getOptionValue(EmbeddedMulgaraOptionParser.HTTP_HOST);
- httpHostName = (httpHost != null || httpConnector == null) ? httpHost : httpConnector.getHost();
-
- // set the port on which to accept HTTP requests
- String httpPort = (String)parser.getOptionValue(EmbeddedMulgaraOptionParser.PORT);
- if (httpPort != null) {
- ServerInfo.setHttpPort(Integer.parseInt(httpPort));
+ if (System.getProperty(DISABLE_HTTP) == null && !mulgaraConfig.getJetty().getDisabled()) {
+ httpEnabled = true;
+ Connector httpConnector = mulgaraConfig.getJetty().getConnector();
+
+ String httpHost = (String)parser.getOptionValue(EmbeddedMulgaraOptionParser.HTTP_HOST);
+ httpHostName = (httpHost != null || httpConnector == null) ? httpHost : httpConnector.getHost();
+
+ // set the port on which to accept HTTP requests
+ String httpPort = (String)parser.getOptionValue(EmbeddedMulgaraOptionParser.PORT);
+ if (httpPort != null) {
+ ServerInfo.setHttpPort(Integer.parseInt(httpPort));
+ } else {
+ if (httpConnector != null) ServerInfo.setHttpPort(httpConnector.getPort());
+ }
} else {
- if (httpConnector != null) ServerInfo.setHttpPort(httpConnector.getPort());
+ httpEnabled = false;
+ httpHostName = "";
}
// set the (RMI) name of the server, preferencing the command line
@@ -737,55 +691,6 @@
/**
- * Creates a HTTP server.
- * @return an HTTP server
- * @throws IOException if the server configuration cannot be found
- * @throws SAXException if the HTTP server configuration file is invalid
- * @throws ClassNotFoundException if the HTTP server configuration file contains a reference to an unkown class
- * @throws NoSuchMethodException if the HTTP server configuration file contains a reference to an unkown method
- * @throws InvocationTargetException if an error ocurrs while trying to configure the HTTP server
- * @throws IllegalAccessException If a class loaded by the server is accessed in an unexpected way.
- */
- public Server createHttpServer() throws IOException, SAXException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
- if (log.isDebugEnabled()) log.debug("Creating HTTP server instance");
-
- // Set the magic logging property for Jetty to use Log4j
- System.setProperty(MortbayLogger.LOGGING_CLASS_PROPERTY, MortbayLogger.class.getCanonicalName());
-
- // create and register a new HTTP server
- Server server;
- if (mulgaraConfig.getJetty().getConnector() == null) {
- // create a default server
- server = new Server(ServerInfo.getHttpPort());
- } else {
- // create a server with a configured connector
- server = new Server();
- addConnector(server);
- }
-
- // add the webapps
- try {
-// server.addHandler(new DefaultHandler());
- addWebServicesWebAppContext(server);
- addWebQueryContext(server);
- } catch (IllegalStateException e) {
- // not fatal, so just log the problem and go on
- log.warn("Unable to start web service", e.getCause());
- }
-
- // add our class loader as the classloader of all contexts, unless this is a webapp in which case we wrap it
- ClassLoader classLoader = this.getClass().getClassLoader();
- for (Handler handler: server.getChildHandlers()) {
- if (handler instanceof WebAppContext) ((WebAppContext)handler).setClassLoader(new WebAppClassLoader(classLoader, (WebAppContext)handler));
- else if (handler instanceof ContextHandler) ((ContextHandler)handler).setClassLoader(classLoader);
- }
-
- // return the server
- return server;
- }
-
-
- /**
* Sets up any system properties needed by system components like JNDI and security.
* @throws IOException if any files embedded within the JAR file cannot be found
*/
@@ -830,123 +735,6 @@
/**
- * Adds a listener to the <code>httpServer</code>. The listener is created and configured
- * according to the Jetty configuration.
- * @param httpServer the server to add the listener to
- * @throws UnknownHostException if an invalid hostname was specified in the Mulgara server configuration
- */
- private void addConnector(Server httpServer) throws UnknownHostException {
- if (httpServer == null) throw new IllegalArgumentException("Null \"httpServer\" parameter");
-
- if (log.isDebugEnabled()) log.debug("Adding socket listener");
-
- // create and configure a listener
- AbstractConnector connector = new BlockingChannelConnector();
- if ((httpHostName != null) && !httpHostName.equals("")) {
- connector.setHost(httpHostName);
- if (log.isDebugEnabled()) log.debug("Servlet container listening on host " + this.getHttpHostName());
- } else {
- httpHostName = getResolvedLocalHost();
- if (log.isDebugEnabled()) log.debug("Servlet container listening on all host interfaces");
- }
-
- // set the listener to the jetty configuration
- Connector jettyConfig = (Connector)mulgaraConfig.getJetty().getConnector();
- connector.setPort(ServerInfo.getHttpPort());
-
- if (jettyConfig.hasMaxIdleTimeMs()) connector.setMaxIdleTime(jettyConfig.getMaxIdleTimeMs());
- if (jettyConfig.hasLowResourceMaxIdleTimeMs()) connector.setLowResourceMaxIdleTime(jettyConfig.getLowResourceMaxIdleTimeMs());
- if (jettyConfig.hasAcceptors()) {
- int acceptors = jettyConfig.getAcceptors();
- if (acceptors > WEIRD_JETTY_THREAD_LIMIT) {
- log.warn("Acceptor threads set beyond HTTP Server limits. Reducing from" + acceptors + " to " + WEIRD_JETTY_THREAD_LIMIT);
- acceptors = WEIRD_JETTY_THREAD_LIMIT;
- }
- connector.setAcceptors(acceptors);
- }
-
- // add the listener to the http server
- httpServer.addConnector(connector);
- }
-
-
- /**
- * Creates the Mulgara Descriptor UI
- * TODO: Rebuild this as a simple servelet
- * @throws IOException if the driver WAR file is not readable
- */
- private void addWebServicesWebAppContext(Server server) throws IOException {
- // get the URL to the WAR file
- URL webServicesWebAppURL = ClassLoader.getSystemResource(WEBAPP_PATH + "/" + WEBSERVICES_WEBAPP);
-
- if (webServicesWebAppURL == null) {
- log.warn("Couldn't find resource: " + WEBAPP_PATH + "/" + WEBSERVICES_WEBAPP);
- return;
- }
-
- String warPath = extractToTemp(WEBAPP_PATH + "/" + WEBSERVICES_WEBAPP);
-
- // Add Descriptors and Axis
- WebAppContext descriptorWARContext = new WebAppContext(server, warPath, "/" + WEBSERVICES_PATH);
-
- // make some attributes available
- descriptorWARContext.setAttribute(BOUND_HOST_NAME_KEY, ServerInfo.getBoundHostname());
- descriptorWARContext.setAttribute(SERVER_MODEL_URI_KEY, ServerInfo.getServerURI().toString());
-
- // log that we're adding the test webapp context
- if (log.isDebugEnabled()) log.debug("Added Web Services webapp context");
- }
-
-
- /**
- * Creates the Mulgara Semantic Store Query Tool (webui).
- * @throws IOException if the driver WAR file i not readable
- */
- private void addWebQueryContext(Server server) throws IOException {
- if (log.isDebugEnabled()) log.debug("Adding WebQuery servlet context");
-
- // create the web query context
- try {
- Servlet servlet = (Servlet)Reflect.newInstance(Class.forName("org.mulgara.webquery.QueryServlet"), getHttpHostName(), getServerName(), (AbstractServer)serverManagement);
- new org.mortbay.jetty.servlet.Context(server, "/" + WEBQUERY_PATH, SESSIONS).addServlet(new ServletHolder(servlet), "/*");
- } catch (ClassNotFoundException e) {
- throw new IllegalStateException("Not configured to use the requested Query servlet");
- }
- }
-
-
- /**
- * Extracts a resource from the environment (a jar in the classpath) and writes
- * this to a file in the working temporary directory.
- * @param resourceName The name of the resource. This is a relative file path in the jar file.
- * @return The absolute path of the file the resource is extracted to, or <code>null</code>
- * if the resource does not exist.
- * @throws IOException If there was an error reading the resource, or writing to the extracted file.
- */
- private String extractToTemp(String resourceName) throws IOException {
- // Find the resource
- URL resourceUrl = ClassLoader.getSystemResource(resourceName);
- if (resourceUrl == null) return null;
-
- // open the resource and the file where it will be copied to
- InputStream in = resourceUrl.openStream();
- File outFile = new File(TempDir.getTempDir(), new File(resourceName).getName());
- log.info("Extracting: " + resourceUrl + " to " + outFile);
- OutputStream out = new FileOutputStream(outFile);
-
- // loop to copy from the resource to the output file
- byte[] buffer = new byte[10240];
- int len;
- while ((len = in.read(buffer)) >= 0) out.write(buffer, 0, len);
- in.close();
- out.close();
-
- // return the file that the resource was extracted to
- return outFile.getAbsolutePath();
- }
-
-
- /**
* Prints the usage instructions for starting the server.
*/
public static void printUsage() {
@@ -1173,9 +961,7 @@
// shut down the SOAP server
try {
- if (server.getHttpServer() != null) {
- server.getHttpServer().stop();
- }
+ if (server.webServices != null) server.webServices.stop();
} catch (Exception e) {
log.error("Couldn't destroy http server", e);
}
Added: trunk/src/jar/server/java/org/mulgara/server/ExceptionList.java
===================================================================
--- trunk/src/jar/server/java/org/mulgara/server/ExceptionList.java (rev 0)
+++ trunk/src/jar/server/java/org/mulgara/server/ExceptionList.java 2008-09-06 11:51:50 UTC (rev 1251)
@@ -0,0 +1,60 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.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.opensource.org/licenses/osl-3.0.txt
+ *
+ * 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.
+ */
+
+package org.mulgara.server;
+
+import java.util.List;
+
+/**
+ * Represents a list of exceptions that may have occurred during a process.
+ *
+ * @created Sep 5, 2008
+ * @author Paul Gearon
+ * @copyright © 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public class ExceptionList extends Exception {
+
+ /** The ID required for serialization. */
+ private static final long serialVersionUID = -4409235739760552916L;
+
+ /** A list of exceptions that accumulated in this condition. */
+ private List<Throwable> exceptionList;
+
+
+ /**
+ * Creates an exception for representing a list of exceptions.
+ * @param exceptions The list of exceptions causing the current condition.
+ */
+ public ExceptionList(List<Throwable> exceptions) {
+ exceptionList = exceptions;
+ }
+
+
+ /**
+ * Creates an exception for representing a list of exceptions.
+ * @param exceptions The list of exceptions causing the current condition.
+ */
+ public ExceptionList(String message, List<Throwable> exceptions) {
+ super(message);
+ exceptionList = exceptions;
+ }
+
+
+ /**
+ * Gets the list of exceptions that caused this condition.
+ * @return The full list of Throwable objects.
+ */
+ public List<Throwable> getCauses() {
+ return exceptionList;
+ }
+}
Added: trunk/src/jar/server/java/org/mulgara/server/HttpServices.java
===================================================================
--- trunk/src/jar/server/java/org/mulgara/server/HttpServices.java (rev 0)
+++ trunk/src/jar/server/java/org/mulgara/server/HttpServices.java 2008-09-06 11:51:50 UTC (rev 1251)
@@ -0,0 +1,357 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.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.opensource.org/licenses/osl-3.0.txt
+ *
+ * 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.
+ */
+
+package org.mulgara.server;
+
+import static org.mortbay.jetty.servlet.Context.SESSIONS;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.Servlet;
+
+import org.apache.log4j.Logger;
+import org.mortbay.jetty.AbstractConnector;
+import org.mortbay.jetty.Handler;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.handler.ContextHandler;
+import org.mortbay.jetty.nio.BlockingChannelConnector;
+import org.mortbay.jetty.servlet.ServletHolder;
+import org.mortbay.jetty.webapp.WebAppClassLoader;
+import org.mortbay.jetty.webapp.WebAppContext;
+import org.mortbay.util.MultiException;
+import org.mulgara.config.Connector;
+import org.mulgara.config.MulgaraConfig;
+import org.mulgara.util.MortbayLogger;
+import org.mulgara.util.Reflect;
+import org.mulgara.util.TempDir;
+import org.mulgara.util.functional.Fn1E;
+import org.mulgara.util.functional.Pair;
+import org.xml.sax.SAXException;
+
+/**
+ * Manages all the HTTP services provided by a Mulgara server.
+ *
+ * @created Sep 5, 2008
+ * @author Paul Gearon
+ * @copyright © 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public class HttpServices {
+
+ /** A virtual typedef for a context starter. */
+ private interface ContextStarter extends Fn1E<Server,Pair<String,String>,IOException> { }
+
+ /** A virtual typedef for a service path. */
+ private class Service extends Pair<String,String> { Service(String f, String s) { super(f,s); } }
+
+ /** The logging category to log to. */
+ protected static Logger logger = Logger.getLogger(HttpServices.class.getName());
+
+ /** The web application file path. */
+ private final static String WEBAPP_PATH = "webapps";
+
+ /** The Web Services web application file. */
+ private final static String WEBSERVICES_WEBAPP = "webservices.war";
+
+ /** The Web Services path. */
+ private final static String WEBSERVICES_PATH = "webservices";
+
+ /** The Web Query path. */
+ private final static String WEBQUERY_PATH = "webui";
+
+ /** The default service path. */
+ private final static String DEFAULT_SERVICE = WEBQUERY_PATH;
+
+ /** The key to the bound host name in the attribute map of the servlet context. */
+ public final static String BOUND_HOST_NAME_KEY = "boundHostname";
+
+ /** Key to the bound server model uri in the attribute map of the servlet context. */
+ public final static String SERVER_MODEL_URI_KEY = "serverModelURI";
+
+ /** The maximum number of acceptors that Jetty can handle. It locks above this number. */
+ private static final int WEIRD_JETTY_THREAD_LIMIT = 24;
+
+ /** The HTTP server instance. */
+ private final Server httpServer;
+
+ /** The configuration for the server. */
+ private final MulgaraConfig config;
+
+ /** The name for the host. */
+ private String hostName;
+
+ /** The host server. This may contain information useful to services. */
+ private final EmbeddedMulgaraServer hostServer;
+
+
+ /**
+ * Creates the web services object.
+ * @param hostServer The Server that started these Web services.
+ * @param hostName The name of the HTTP host this object is setting up.
+ * @param config The configuration to use.
+ * @throws IOException Exception setting up with files or network.
+ * @throws SAXException Problem reading XML configurations.
+ * @throws ClassNotFoundException An expected class was not found.
+ * @throws NoSuchMethodException A configured class was not built as expected.
+ * @throws InvocationTargetException A configured class did not behave as expected.
+ * @throws IllegalAccessException A configured class was not accessible.
+ */
+ public HttpServices(EmbeddedMulgaraServer hostServer, String hostName, MulgaraConfig config) throws IOException, SAXException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ this.hostServer = hostServer;
+ this.config = config;
+ this.hostName = hostName;
+ httpServer = createHttpServer();
+ }
+
+
+ /**
+ * Starts the web server and all services.
+ * @throws ExceptionList Caused by a MultiException in the HTTP Server.
+ * @throws Exception Both the server and the services are able to throw exceptions.
+ */
+ @SuppressWarnings("unchecked")
+ public void start() throws ExceptionList, Exception {
+ try {
+ httpServer.start();
+ } catch (MultiException e) {
+ throw new ExceptionList(e.getThrowables());
+ }
+ }
+
+
+ /**
+ * Stops the web server and all services.
+ * @throws Exception Both the server and the services are able to throw exceptions.
+ */
+ public void stop() throws Exception {
+ httpServer.stop();
+ }
+
+
+ /**
+ * Creates an HTTP server.
+ * @return an HTTP server
+ * @throws IOException if the server configuration cannot be found
+ * @throws SAXException if the HTTP server configuration file is invalid
+ * @throws ClassNotFoundException if the HTTP server configuration file contains a reference to an unkown class
+ * @throws NoSuchMethodException if the HTTP server configuration file contains a reference to an unkown method
+ * @throws InvocationTargetException if an error ocurrs while trying to configure the HTTP server
+ * @throws IllegalAccessException If a class loaded by the server is accessed in an unexpected way.
+ */
+ public Server createHttpServer() throws IOException, SAXException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ if (logger.isDebugEnabled()) logger.debug("Creating HTTP server instance");
+
+ // Set the magic logging property for Jetty to use Log4j
+ System.setProperty(MortbayLogger.LOGGING_CLASS_PROPERTY, MortbayLogger.class.getCanonicalName());
+
+ // create and register a new HTTP server
+ Server server;
+ if (config.getJetty().getConnector() == null) {
+ // create a default server
+ server = new Server(ServerInfo.getHttpPort());
+ } else {
+ // create a server with a configured connector
+ server = new Server();
+ addConnector(server);
+ }
+
+ // Accumulator for all the services
+ Map<String,String> services = new HashMap<String,String>();
+
+ // start all the configured services.
+ for (ContextStarter starter: getContextStarters()) {
+ try {
+ starter.fn(server).add(services);
+ } catch (IllegalStateException e) {
+ // not fatal, so just log the problem and go on
+ logger.warn("Unable to start web service", e.getCause());
+ }
+ }
+
+ // we have all the services, so now instantiate the service listing service
+ addWebServiceListingContext(server, services);
+
+ // add our class loader as the classloader of all contexts, unless this is a webapp in which case we wrap it
+ ClassLoader classLoader = this.getClass().getClassLoader();
+ for (Handler handler: server.getChildHandlers()) {
+ if (handler instanceof WebAppContext) ((WebAppContext)handler).setClassLoader(new WebAppClassLoader(classLoader, (WebAppContext)handler));
+ else if (handler instanceof ContextHandler) ((ContextHandler)handler).setClassLoader(classLoader);
+ }
+
+ // return the server
+ return server;
+ }
+
+
+ /**
+ * Creates a list of functions for starting contexts.
+ * <strong>This defines the list of services to be run.</strong>
+ * @return A list that can start all the configured contexts.
+ */
+ private List<ContextStarter> getContextStarters() {
+ List<ContextStarter> starters = new ArrayList<ContextStarter>();
+ starters.add(new ContextStarter() { public Service fn(Server s) throws IOException {
+ return addWebServicesWebAppContext(s);
+ } });
+ starters.add(new ContextStarter() { public Service fn(Server s) throws IOException {
+ return addWebQueryContext(s);
+ } });
+ return starters;
+ }
+
+
+ /**
+ * Adds a listener to the <code>httpServer</code>. The listener is created and configured
+ * according to the Jetty configuration.
+ * @param httpServer the server to add the listener to
+ * @throws UnknownHostException if an invalid hostname was specified in the Mulgara server configuration
+ */
+ private void addConnector(Server httpServer) throws UnknownHostException {
+ if (httpServer == null) throw new IllegalArgumentException("Null \"httpServer\" parameter");
+
+ if (logger.isDebugEnabled()) logger.debug("Adding socket listener");
+
+ // create and configure a listener
+ AbstractConnector connector = new BlockingChannelConnector();
+ if ((hostName != null) && !hostName.equals("")) {
+ connector.setHost(hostName);
+ if (logger.isDebugEnabled()) logger.debug("Servlet container listening on host " + hostName);
+ } else {
+ hostName = EmbeddedMulgaraServer.getResolvedLocalHost();
+ if (logger.isDebugEnabled()) logger.debug("Servlet container listening on all host interfaces");
+ }
+
+ // set the listener to the jetty configuration
+ Connector jettyConfig = (Connector)config.getJetty().getConnector();
+ connector.setPort(ServerInfo.getHttpPort());
+
+ if (jettyConfig.hasMaxIdleTimeMs()) connector.setMaxIdleTime(jettyConfig.getMaxIdleTimeMs());
+ if (jettyConfig.hasLowResourceMaxIdleTimeMs()) connector.setLowResourceMaxIdleTime(jettyConfig.getLowResourceMaxIdleTimeMs());
+ if (jettyConfig.hasAcceptors()) {
+ int acceptors = jettyConfig.getAcceptors();
+ if (acceptors > WEIRD_JETTY_THREAD_LIMIT) {
+ logger.warn("Acceptor threads set beyond HTTP Server limits. Reducing from" + acceptors + " to " + WEIRD_JETTY_THREAD_LIMIT);
+ acceptors = WEIRD_JETTY_THREAD_LIMIT;
+ }
+ connector.setAcceptors(acceptors);
+ }
+
+ // add the listener to the http server
+ httpServer.addConnector(connector);
+ }
+
+
+ /**
+ * Creates the Mulgara Descriptor UI
+ * @throws IOException if the driver WAR file is not readable
+ */
+ private Service addWebServicesWebAppContext(Server server) throws IOException {
+ // get the URL to the WAR file
+ URL webServicesWebAppURL = ClassLoader.getSystemResource(WEBAPP_PATH + "/" + WEBSERVICES_WEBAPP);
+
+ if (webServicesWebAppURL == null) {
+ logger.warn("Couldn't find resource: " + WEBAPP_PATH + "/" + WEBSERVICES_WEBAPP);
+ return null;
+ }
+
+ String warPath = extractToTemp(WEBAPP_PATH + "/" + WEBSERVICES_WEBAPP);
+
+ // Add Descriptors and Axis
+ String webPath = "/" + WEBSERVICES_PATH;
+ WebAppContext descriptorWARContext = new WebAppContext(server, warPath, webPath);
+
+ // make some attributes available
+ descriptorWARContext.setAttribute(BOUND_HOST_NAME_KEY, ServerInfo.getBoundHostname());
+ descriptorWARContext.setAttribute(SERVER_MODEL_URI_KEY, ServerInfo.getServerURI().toString());
+
+ // log that we're adding the test webapp context
+ if (logger.isDebugEnabled()) logger.debug("Added Web Services webapp context");
+ return new Service("Web Services", webPath);
+ }
+
+
+ /**
+ * Creates the Mulgara Semantic Store Query Tool (webui).
+ * @throws IOException if the servlet cannot talk to the network.
+ */
+ private Service addWebQueryContext(Server server) throws IOException {
+ if (logger.isDebugEnabled()) logger.debug("Adding WebQuery servlet context");
+
+ // create the web query context
+ try {
+ AbstractServer serverMBean = (AbstractServer)hostServer.getServerMBean();
+ String rmiName = hostServer.getServerName();
+ Servlet servlet = (Servlet)Reflect.newInstance(Class.forName("org.mulgara.webquery.QueryServlet"), hostName, rmiName, serverMBean);
+ String webPath = "/" + WEBQUERY_PATH;
+ new org.mortbay.jetty.servlet.Context(server, webPath, SESSIONS).addServlet(new ServletHolder(servlet), "/*");
+ return new Service("User Interface", webPath);
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException("Not configured to use the requested Query servlet");
+ }
+ }
+
+
+ /**
+ * Creates the servlet used to list the other servlets.
+ * @param server The server to register this servlet with.
+ * @param services The list of service names and paths.
+ * @throws IOException If the servlet cannot talk to the network.
+ */
+ private void addWebServiceListingContext(Server server, Map<String,String> services) throws IOException {
+ if (logger.isDebugEnabled()) logger.debug("Adding the service lister context");
+ Servlet servlet = new ServiceListingServlet(services, "/" + DEFAULT_SERVICE);
+ new org.mortbay.jetty.servlet.Context(server, "/", SESSIONS).addServlet(new ServletHolder(servlet), "/*");
+ }
+
+
+ /**
+ * Extracts a resource from the environment (a jar in the classpath) and writes
+ * this to a file in the working temporary directory.
+ * @param resourceName The name of the resource. This is a relative file path in the jar file.
+ * @return The absolute path of the file the resource is extracted to, or <code>null</code>
+ * if the resource does not exist.
+ * @throws IOException If there was an error reading the resource, or writing to the extracted file.
+ */
+ private String extractToTemp(String resourceName) throws IOException {
+ // Find the resource
+ URL resourceUrl = ClassLoader.getSystemResource(resourceName);
+ if (resourceUrl == null) return null;
+
+ // open the resource and the file where it will be copied to
+ InputStream in = resourceUrl.openStream();
+ File outFile = new File(TempDir.getTempDir(), new File(resourceName).getName());
+ logger.info("Extracting: " + resourceUrl + " to " + outFile);
+ OutputStream out = new FileOutputStream(outFile);
+
+ // loop to copy from the resource to the output file
+ byte[] buffer = new byte[10240];
+ int len;
+ while ((len = in.read(buffer)) >= 0) out.write(buffer, 0, len);
+ in.close();
+ out.close();
+
+ // return the file that the resource was extracted to
+ return outFile.getAbsolutePath();
+ }
+
+}
Modified: trunk/src/jar/server/java/org/mulgara/server/ServletMulgaraServer.java
===================================================================
--- trunk/src/jar/server/java/org/mulgara/server/ServletMulgaraServer.java 2008-09-06 11:50:30 UTC (rev 1250)
+++ trunk/src/jar/server/java/org/mulgara/server/ServletMulgaraServer.java 2008-09-06 11:51:50 UTC (rev 1251)
@@ -38,7 +38,6 @@
// servlet packages
import javax.servlet.ServletException;
-import javax.servlet.SingleThreadModel;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -75,229 +74,147 @@
* </p>
*
* @created 2002-03-04
- *
* @author Tom Adams
- *
- * @version $Revision: 1.10 $
- *
- * @modified $Date: 2005/01/13 01:55:32 $ by $Author: raboczi $
- *
* @company <A href="mailto:info at PIsoftware.com">Plugged In Software</A>
- *
- * @copyright ©2002 <a href="http://www.pisoftware.com/">Plugged In
- * Software Pty Ltd</a>
- *
+ * @copyright ©2002 <a href="http://www.pisoftware.com/">Plugged In Software Pty Ltd</a>
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
-public class ServletMulgaraServer extends HttpServlet implements
- SingleThreadModel {
+public class ServletMulgaraServer extends HttpServlet {
- //
- // Constants
- //
+ /** Used for serializing - an unlikely occurance. */
+ private static final long serialVersionUID = -5680050587689401924L;
- /**
- * the logging category to log to
- */
- private final static Logger log = Logger.getLogger(ServletMulgaraServer.class
- .getName());
+ /** The logging category to log to */
+ private final static Logger log = Logger.getLogger(ServletMulgaraServer.class.getName());
- /**
- * the key to retreive the Mulgara server name
- */
+ /** the key to retreive the Mulgara server name */
private final static String MULGARA_CONFIG_SERVERNAME = "mulgara.config.servername";
- /**
- * the key to retrieve the database persistence path
- */
+ /** the key to retrieve the database persistence path */
private final static String MULGARA_CONFIG_PERSISTENCE_PATH = "mulgara.config.persistencepath";
- /**
- * the key to retreive the Mulgara security policy file
- */
- private final static String MULGARA_SECURITY_POLICY_PATH = "mulgara.security.policy";
-
- /**
- * the key to retreive the Mulgara host name
- */
+ /** the key to retreive the Mulgara host name */
private final static String MULGARA_HOSTNAME = "mulgara.hostname";
- /**
- * the key to retreive the Mulgara LDAP security file
- */
- private final static String LDAP_SECURITY_PATH = "mulgara.security.ldap";
-
- /**
- * the key to retreive the Mulgara LDAP security file
- */
+ /** the key to retreive the Mulgara LDAP security file */
private final static String MULGARA_LOG4J_CONFIG = "mulgara.log4j.config";
- /**
- * the key to retreive the lucene index directory
- */
+ /** the key to retreive the lucene index directory */
private final static String LUCENE_INDEX_DIR = "lucene.index.dir";
- //
- // Members
- //
- /**
- * the database of this server
- */
+ /** the database of this server */
private static Database database = null;
- //
- // Public API
- //
/**
* Sets the database of this server.
- *
* @param database the database of this server
*/
public static void setDatabase(Database database) {
-
ServletMulgaraServer.database = database;
-
}
+
/**
* Sets the database of this server.
- *
* @return The Database value
*/
public static Database getDatabase() {
-
return ServletMulgaraServer.database;
}
- //
- // Methods overriding Servlet
- //
/**
* Creates a new Mulgara database, and stores it in the servlet context.
- *
- * @throws ServletException if the Mulgara database cannot be created for some
- * reason
+ * @throws ServletException if the Mulgara database cannot be created for some reason
*/
public void init() throws ServletException {
BasicConfigurator.configure();
-
try {
-
// get the loction of the logging configuration file
- String log4jConfigPath = this
- .getRealPath(ServletMulgaraServer.MULGARA_LOG4J_CONFIG);
+ String log4jConfigPath = getRealPath(ServletMulgaraServer.MULGARA_LOG4J_CONFIG);
//this.getResourceURL(ServletMulgaraServer.MULGARA_LOG4J_CONFIG);
- if (log4jConfigPath == null) {
+ if (log4jConfigPath == null) throw new IOException("Unable to retrieve log4j configuration file");
- throw new IOException("Unable to retrieve log4j configuration file");
- }
-
// load the logging configuration
- this.loadLoggingConfig((new File(log4jConfigPath)).toURL());
- }
- catch (Exception e) {
-
+ loadLoggingConfig((new File(log4jConfigPath)).toURL());
+ } catch (Exception e) {
// log the error
log.error(e);
-
// wrap it in a servlet exception
throw new ServletException(e);
}
// log what we're doing
- if (log.isInfoEnabled()) {
+ if (log.isInfoEnabled()) log.info("Initialising Mulgara server servlet");
- log.info("Initialising Mulgara server servlet");
- }
-
// log that we've created a new server
- if (log.isDebugEnabled()) {
+ if (log.isDebugEnabled()) log.debug("Created servlet-wrapped Mulgara server");
- log.debug("Created servlet-wrapped Mulgara server");
- }
-
// if it we don't have one already, create a new database
if (ServletMulgaraServer.getDatabase() == null) {
-
- ServletMulgaraServer.setDatabase(this.createDatabase());
+ ServletMulgaraServer.setDatabase(createDatabase());
}
}
/**
* Closes the database.
- *
*/
- public void destroy() {
+ public synchronized void destroy() {
- if (ServletMulgaraServer.getDatabase() != null) {
-
+ if (getDatabase() != null) {
// log that we're stopping the database
- if (log.isInfoEnabled()) {
+ if (log.isInfoEnabled()) log.info("Stopping Mulgara server");
- log.info("Stopping Mulgara server");
- }
-
- ServletMulgaraServer.getDatabase().close();
- ServletMulgaraServer.setDatabase(null);
+ getDatabase().close();
+ setDatabase(null);
}
}
+
/**
- * Closes the database.
- *
- * @throws Throwable EXCEPTION TO DO
+ * Closes the database at the last moment.
+ * @throws Throwable General catch-all for closing problems.
*/
protected void finalize() throws Throwable {
-
- // delgate to destroy
- this.destroy();
+ destroy();
}
+
/**
* As this servlet cannot handle requests (its only job is to start create a
* database), this method always throws an exception.
- *
- * @param req PARAMETER TO DO
- * @param res PARAMETER TO DO
- * @throws ServletException always
+ * @param req The request.
+ * @param res The response to the client.
+ * @throws ServletException always thrown.
*/
- protected void service(HttpServletRequest req, HttpServletResponse res)
- throws ServletException {
-
+ protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException {
throw new ServletException("Mulgara server servlet does not handle requests");
}
+
/**
* Returns the URI of the server based on the server name.
- *
* @param serverName the name of the server
* @return the URI of the server
* @throws URISyntaxException if the URI cannot be created
*/
private URI getServerURI(String serverName) throws URISyntaxException {
- String hostname = this.getServletContext()
- .getInitParameter(MULGARA_HOSTNAME);
+ String hostname = getServletContext().getInitParameter(MULGARA_HOSTNAME);
// attempt to determine the hostname if not supplied by servlet
if (hostname == null || hostname.trim().length() == 0) {
-
hostname = "localhost";
try {
-
hostname = InetAddress.getLocalHost().getHostName();
log.info("Obtained " + hostname + " automatically for server");
-
- }
- catch (Exception e) {
-
+ } catch (Exception e) {
log.warn("Problem getting host name -> using localhost");
}
}
@@ -315,8 +232,7 @@
* deployment. These are then mapped to the system temp directory and the
* user's current directory respectively by this method. </p>
*
- * @param aliasedPath the persistence path as it appears in the configuration
- * file
+ * @param aliasedPath the persistence path as it appears in the configuration file
* @return the unaliased persistence path
*/
private String getPersistencePath(String aliasedPath) {
@@ -325,14 +241,10 @@
String persistencePath = aliasedPath;
// unalias the path (if we need to)
- if ((aliasedPath == null) || aliasedPath.equals("")
- || aliasedPath.equalsIgnoreCase(".")) {
-
+ if ((aliasedPath == null) || aliasedPath.equals("") || aliasedPath.equalsIgnoreCase(".")) {
// use the current directory
persistencePath = System.getProperty("user.dir");
- }
- else if (aliasedPath.equalsIgnoreCase("temp")) {
-
+ } else if (aliasedPath.equalsIgnoreCase("temp")) {
persistencePath = System.getProperty("java.io.tmpdir");
}
@@ -344,9 +256,7 @@
/**
* Returns the real location of a file path specified by the <code>parameter</code>
* in the servlet context.
- *
- * @param param the init param in the servlet context to find the real
- * location of
+ * @param param the init param in the servlet context to find the real location of
* @return the real location of the given param
*/
private String getRealPath(String param) {
@@ -356,13 +266,9 @@
// try to find the real path
if (filePath != null) {
-
// log that we've found the config file
- if (log.isDebugEnabled()) {
+ if (log.isDebugEnabled()) log.debug("Found file location " + filePath + " for param " + param);
- log.debug("Found file location " + filePath + " for param " + param);
- }
-
// get the real path
filePath = this.getServletContext().getRealPath(filePath);
}
@@ -371,139 +277,94 @@
return filePath;
}
- // getDatabase()
- //
- // Internal methods
- //
/**
* Creates a new database.
- *
- * @return RETURNED VALUE TO DO
- * @throws ServletException if the database could not be created
+ * @return The created database.
+ * @throws ServletException if the database could not be created.
*/
- private Database createDatabase() throws ServletException {
-
+ private synchronized Database createDatabase() throws ServletException {
try {
-
// configure the system properties
- if (log.isDebugEnabled()) {
+ if (log.isDebugEnabled()) log.debug("Configuring system properties");
- log.debug("Configuring system properties");
- }
+ configureSystemProperties();
- this.configureSystemProperties();
-
// get params we'll need to create the server
- String persistencePath = this.getPersistencePath(this.getServletContext()
- .getInitParameter(MULGARA_CONFIG_PERSISTENCE_PATH));
- String serverName = this.getServletContext().getInitParameter(
- MULGARA_CONFIG_SERVERNAME);
+ String tmpConfiguredPath = getServletContext().getInitParameter(MULGARA_CONFIG_PERSISTENCE_PATH);
+ String persistencePath = getPersistencePath(tmpConfiguredPath);
+ String serverName = getServletContext().getInitParameter(MULGARA_CONFIG_SERVERNAME);
// throw an error if anything is null
- if (serverName == null) {
+ if (serverName == null) throw new ServletException("Server name not in deployment descriptor");
- throw new ServletException("Server name not in deployment descriptor");
- }
-
- // end if
// get the server's URI and the database state path
- URI serverURI = this.getServerURI(serverName);
+ URI serverURI = getServerURI(serverName);
File statePath = new File(new File(persistencePath), serverName);
// create the state path if needed
- if (!statePath.exists()) {
+ if (!statePath.exists()) statePath.mkdirs();
- statePath.mkdirs();
- }
-
- // end if
// log that we're creating a Mulgara server
- if (log.isInfoEnabled()) {
+ if (log.isInfoEnabled()) log.info("Starting Mulgara server at " + serverURI + " in directory " + statePath);
- log.info("Starting Mulgara server at " + serverURI + " in directory "
- + statePath);
- }
-
// return the database
SessionFactoryFactory factory = new SessionFactoryFactory();
+ SessionFactory sessionFactory = factory.newSessionFactory(serverURI, statePath);
- SessionFactory sessionFactory = factory.newSessionFactory(serverURI,
- statePath);
+ return (Database)sessionFactory;
- return (Database) sessionFactory;
- }
- catch (SessionFactoryException sfe) {
+ } catch (SessionFactoryException sfe) {
// log the error
log.error(sfe);
-
// wrap it in a servlet exception
throw new ServletException(sfe);
- }
- catch (UnknownHostException uhe) {
-
+ } catch (UnknownHostException uhe) {
// log the error
log.error(uhe);
-
// wrap it in a servlet exception
throw new ServletException(uhe);
- }
- catch (IOException ioe) {
-
+ } catch (IOException ioe) {
// log the error
log.error(ioe);
-
// wrap it in a servlet exception
throw new ServletException(ioe);
- }
- catch (URISyntaxException use) {
-
+ } catch (URISyntaxException use) {
// log the error
log.error(use);
-
// wrap it in a servlet exception
throw new ServletException(use);
}
-
}
+
/**
* Sets up any system properties needed by components.
- *
- * @throws IOException if any files embedded within the JAR file cannot be
- * found
+ * @throws IOException if any files embedded within the JAR file cannot be found
*/
private void configureSystemProperties() throws IOException {
-
// set the system properties needed
System.setProperty("org.mulgara.xml.ResourceDocumentBuilderFactory",
"org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
// set the lucene index
- String luceneIndex = this.getServletContext().getInitParameter(
- ServletMulgaraServer.LUCENE_INDEX_DIR);
+ String luceneIndex = this.getServletContext().getInitParameter(ServletMulgaraServer.LUCENE_INDEX_DIR);
if (luceneIndex != null) {
-
System.setProperty(ServletMulgaraServer.LUCENE_INDEX_DIR, luceneIndex);
-
}
}
+
/**
* Loads the embedded logging configuration from an external URL.
- *
* @param loggingConfig the URL of the logging configuration file
*/
private void loadLoggingConfig(URL loggingConfig) {
// validate the loggingConfig parameter
- if (loggingConfig == null) {
+ if (loggingConfig == null) throw new IllegalArgumentException("Null \"loggingConfig\" parameter");
- throw new IllegalArgumentException("Null \"loggingConfig\" parameter");
- }
-
- // end if
// configure the logging service
PropertyConfigurator.configure(loggingConfig);
log.info("Using logging configuration from " + loggingConfig);
Modified: trunk/src/war/descriptor/index.jsp
===================================================================
--- trunk/src/war/descriptor/index.jsp 2008-09-06 11:50:30 UTC (rev 1250)
+++ trunk/src/war/descriptor/index.jsp 2008-09-06 11:51:50 UTC (rev 1251)
@@ -1,6 +1,6 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<%@ page import="java.net.*, org.mulgara.server.EmbeddedMulgaraServer" %>
+<%@ page import="java.net.*, org.mulgara.server.HttpServices" %>
<%
String hostname = null;
@@ -8,7 +8,7 @@
try {
if (hostname == null) {
- hostname = (String)getServletContext().getAttribute(EmbeddedMulgaraServer.BOUND_HOST_NAME_KEY);
+ hostname = (String)getServletContext().getAttribute(HttpServices.BOUND_HOST_NAME_KEY);
// determine the requesting URL
currentHostUrl = HttpUtils.getRequestURL(request).toString();
@@ -28,7 +28,7 @@
}
String URL2Here = currentHostUrl.substring(0, currentHostUrl.length() - "index.jsp".length());
-String descriptorModel = (String)getServletContext().getAttribute(EmbeddedMulgaraServer.SERVER_MODEL_URI_KEY) + "#descriptors";
+String descriptorModel = (String)getServletContext().getAttribute(HttpServices.SERVER_MODEL_URI_KEY) + "#descriptors";
%>
More information about the Mulgara-svn
mailing list