package com.dbxml.db.server;
/*
* dbXML - Native XML Database
* Copyright (c) 1999-2006 The dbXML Group, L.L.C.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* $Id: Server.java,v 1.5 2006/02/02 19:04:15 bradford Exp $
*/
import com.dbxml.db.server.labrador.ServerProxy;
import com.dbxml.labrador.ID;
import com.dbxml.labrador.objects.ObjectResolver;
import com.dbxml.util.Configuration;
import com.dbxml.util.LoggingStream;
import com.dbxml.xml.dom.DOMHelper;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.w3c.dom.Document;
import java.io.BufferedOutputStream;
/**
* The Server class is the glue that holds the dbXML server together.
* It is used to bootstrap the server as well as to instantiate and
* start the various system services.
*/
public final class Server {
// Configuration Defaults
private static final String LOG_FILE = "logs/dbXML.out";
private static final String CONFIG_FILE = "config/system.xml";
private static final String SERVER = "server";
private static final String SERVICES = "services";
private static final String LOGGING = "logging";
private static final String LOG = "logfile";
private static Server instance;
private Configuration config;
private Task finalizer;
private Thread scheduler;
private boolean running;
private List tasks = Collections.synchronizedList(new ArrayList()); // Of TaskInfo
private ServiceManager services = new ServiceManager();
public static void main(String[] args) {
System.out.println(dbXML.VersionString);
if ( args.length > 0 )
new Server(args[0]);
else {
File cfg = new File(new File(System.getProperty(dbXML.PROP_DBXML_HOME)), CONFIG_FILE);
new Server(cfg.getAbsolutePath());
}
}
public static void stop(String[] args) {
getInstance().shutDown(0);
}
public Server() {
this("system.xml");
}
public Server(final String name) {
instance = this;
// Read in the configuration
Document tmp = null;
try {
tmp = DOMHelper.parse(new File(name));
}
catch ( Exception e ) {
System.err.println("FATAL ERROR: Reading configuration file '" + name + "'");
e.printStackTrace(System.err);
System.exit(1);
}
final Document doc = tmp;
config = new Configuration(doc);
Configuration serverConfig = config.getChild(SERVER);
try {
boolean logging = config.getBooleanAttribute(LOGGING, true);
if ( logging ) {
// Hijack System.out and System.err
File logFile = null;
String log = config.getAttribute(LOG);
if ( log != null && log.length() > 0 )
logFile = new File(log);
else
logFile = new File(new File(System.getProperty(dbXML.PROP_DBXML_HOME)), LOG_FILE);
if ( logFile != null ) {
boolean header = !logFile.exists();
System.out.println("Logging to " + logFile.getPath());
FileOutputStream fos = new FileOutputStream(logFile.getPath(), true);
LoggingStream ls = new LoggingStream(fos);
PrintStream ps = new PrintStream(ls, true);
if ( header ) {
String s = dbXML.Title + " " + dbXML.Version + " Console Log File";
ps.println(s);
StringBuffer sb = new StringBuffer(s.length());
for ( int i = 0; i < s.length(); i++ )
sb.append('-');
ps.println(sb.toString());
}
System.setOut(ps);
System.setErr(ps);
}
}
else
System.out.println("Logging to standard output");
}
catch ( Exception e ) {
System.err.println("FATAL ERROR: Can't override standard logging");
e.printStackTrace(System.err);
System.exit(1);
}
System.out.println("Server startup");
// Configure the Service Manager
services.setServer(this);
services.setConfig(serverConfig.getChild(SERVICES));
if ( !services.startServices() ) {
System.err.println("FATAL ERROR: Service manager could not be started");
System.exit(1);
}
ObjectResolver.register(new ID("dbXML"), new ServerProxy(this));
// Register the shutdown handler
Runtime.getRuntime().addShutdownHook(new ShutdownHandler());
// Start The Scheduling Daemon
scheduler = new TaskScheduler();
scheduler.start();
running = true;
}
/**
* getInstance returns the VM's Server instance. Only one instance
* reference is allowed per VM, and any new instance will shadow
* the previous one.
*
* @return The Server instance
*/
public static Server getInstance() {
return instance;
}
/**
* addTask adds an interval Task to the Server instance or overrides
* a previously defined interval.
*
* @param task The Task to add
* @param interval The Interval (milliseconds)
*/
public synchronized void addTask(Task task, long interval) {
int size = tasks.size();
for ( int i = 0; i < size; i++ ) {
TaskInfo info = (TaskInfo)tasks.get(i);
if ( task == info.task ) {
info.interval = interval;
return;
}
}
tasks.add(new TaskInfo(task, interval));
}
/**
* removeTasks removes an interval Task from the Server instance.
*
* @param task The task to remove
*/
public synchronized void removeTask(Task task) {
int size = tasks.size();
for ( int i = 0; i < size; i++ ) {
TaskInfo info = (TaskInfo)tasks.get(i);
if ( task == info.task ) {
tasks.remove(i);
return;
}
}
}
/**
* shutDown shuts down the Server instance. This will trigger a shutdown
* handler that will cleanly stop any active Services.
*
* @param exitCode The shutdown exit code
*/
public void shutDown(int exitCode) {
System.exit(exitCode);
}
/**
* isRunning returns whether or not the Services are all running.
*
* @return Whether or not the Services are running.
*/
public boolean isRunning() {
return running;
}
/**
* listServices lists the names of all registered Services.
*
* @return The Service list
*/
public String[] listServices() {
return services.listServices();
}
/**
* getService retrieves a registered Service by name.
*
* @param name The Service name
* @return The Service
*/
public Service getService(String name) {
return services.getService(name);
}
/**
* ShutdownHandler
*/
private class ShutdownHandler extends Thread {
public ShutdownHandler() {
super("Shutdown Handler");
}
public void run() {
System.out.println();
removeTask(finalizer);
services.stopServices();
services.dispose();
System.out.println("Server shutdown");
}
}
/**
* TaskScheduler
*/
private class TaskScheduler extends Thread {
public TaskScheduler() {
super("Task Scheduler");
setDaemon(false);
}
public void run() {
TaskInfo info;
long wait = 5000;
long time = 0;
long done = 0;
long diff = 0;
int size = 0;
int i = 0;
while ( true ) {
try {
wait = 5000;
size = tasks.size();
for ( i = 0; i < size; i++ ) {
info = (TaskInfo)tasks.get(i);
time = System.currentTimeMillis();
if ( time >= info.lastrun + info.interval ) {
try {
info.task.runTask();
}
catch ( Exception e ) {
if ( info.error == false ) {
info.error = true;
System.err.println("TASK ERROR: " + e);
}
}
done = System.currentTimeMillis();
info.lastrun = time;
diff = done - time;
if ( info.interval - diff < wait )
wait = info.interval - diff;
}
else {
diff = (info.lastrun + info.interval) - time;
if ( diff > 0 && diff < wait )
wait = diff;
}
}
Thread.sleep(wait);
}
catch ( Exception e ) {
}
}
}
}
/**
* TaskInfo
*/
private class TaskInfo {
public Task task;
public long interval = 0;
public long lastrun = 0;
public boolean error;
public TaskInfo(Task task, long interval) {
this.task = task;
this.interval = interval;
this.lastrun = System.currentTimeMillis();
}
}
}