/*************************************************
*
* = PACKAGE
* JACE.ServiceConfigurator
*
* = FILENAME
* ServiceConfig.java
*
* Services can be suspended, resumed, removed, and reloaded. Reloading requires that
* the user calls a prepareForReload method after removing a service. You can't access
* the ServiceObjects that are loaded directly -- anything loaded with a class loader
* must be wrapped and have its methods called via reflection. This is because a
* loaded class doesn't exist in the same space as one loaded with the system loader.
*
*
*
*@author Prashant Jain, Everett Anderson
*
*************************************************/
package JACE.ServiceConfigurator;
import java.io.*;
import java.util.*;
import java.net.*;
import JACE.OS.*;
import JACE.Misc.*;
/**
*
* TITLE
* Provide the base class that supplies common server daemon
* operations. Also provides a global point for interacting with
* the service repository.
*/
public class ServiceConfig
{
/** Begins the process of loading a service configurator file:
* parses the command line and calls either loadOldConfigFile or
* processDirectives depending on whether or not the user wants
* to try to load a C++ svc.conf file.
*/
public static int open (String [] args) throws FileNotFoundException, IOException, ClassNotFoundException, IllegalAccessException, InstantiationException
{
// Parse the command line
ServiceConfig.parseArgs (args);
// Create a repository and class loader
if (ServiceConfig.svcRep_ == null)
ServiceConfig.svcRep_ = new ServiceRepository ();
if (ServiceConfig.loader_ == null)
ServiceConfig.loader_ = new ServiceLoader();
if (ServiceConfig.oldConfigFormat_)
return ServiceConfig.loadOldConfigFile();
else
return ServiceConfig.processDirectives ();
}
/** Parses the command line
* Valid command line options:
* -b Run as a daemon (not implemented yet)
* -d Debug mode
* -n No defaults
* -o Attempt to load a C++ ACE service config file
* -f Load services in the given file [see below for info]
*
*/
protected static void parseArgs (String [] args)
{
GetOpt getopt = new GetOpt (args, "bdnf:o:");
for (int c; (c = getopt.next ()) != -1; )
switch (c)
{
case 'b':
// Note: not supported yet!
ServiceConfig.beADaemon_ = true;
break;
case 'd':
ServiceConfig.debug_ = true;
break;
case 'n':
ServiceConfig.noDefaults_ = true;
break;
case 'o':
ServiceConfig.oldConfigFormat_ = true;
break;
case 'f':
ServiceConfig.serviceConfigFile_ = getopt.optarg ();
break;
default:
ACE.ERROR ((char ) c + " is not a ServiceConfig option");
break;
}
}
/** Called by ParseNode subclass
* Asks the Service Repository to spend a service
*/
public static int suspend (String name)
{
return svcRep_.suspend(name);
}
/** Called by ParseNode subclass
* Asks the Service Repository to resume a service
*/
public static int resume (String name)
{
return svcRep_.resume(name);
}
/** Called by ParseNode subclass
* Asks the Service Repository to remove a serivce
*/
public static int remove (String name)
{
return svcRep_.remove(name);
}
/** Should be called before the user wants to reload
* a service. This calls garbage collection to
* (hopefully) obliterate the names of any unused
* service classes, and creates a new instance
* of the ClassLoader so there won't be problems
* reloading.
*/
public static void prepareForReload()
{
ServiceConfig.loader_ = new ServiceLoader();
System.gc();
}
/**
* Parse a service configurator file, creating classes as necessary
*
* This is getting too complicated -- since CUP and JLex are available, it would be nice to
* develop a grammar for this. Unfortunately, there may be file problems when trying to get
* CUP and JLex to produce more than one parser per program.
*
* Current formats:
*
* load Service_Object ""
*
* resume
* suspend
* remove
*
*/
protected static int processDirectives () throws FileNotFoundException, IOException, ClassNotFoundException, IllegalAccessException, InstantiationException
{
if (ServiceConfig.serviceConfigFile_ == null)
ServiceConfig.serviceConfigFile_ = "svc.conf";
ACE.DEBUG("Processing directives in file " + ServiceConfig.serviceConfigFile_);
File configFile = new File (ServiceConfig.serviceConfigFile_);
// Check if file exists and is a normal file
if (!configFile.exists () || !configFile.isFile ())
throw new FileNotFoundException ("File " + ServiceConfig.serviceConfigFile_ + " not found");
// Check if the file is readable
if (!configFile.canRead ())
throw new IOException ("File " + ServiceConfig.serviceConfigFile_ + " not readable");
// Set up the stream
FileInputStream fileIn = new FileInputStream (configFile);
// Parse the file
StreamTokenizer in = new StreamTokenizer (fileIn);
// Set '#' as comment character to be ignored and set '/' as
// ordinary character (was original comment character)
// in.commentChar ('#');
in.ordinaryChar ('/');
// Set characters in ASCII range 33 to 47, ASCII range 91 to 96,
// and ASCII range 123 to 126 as ordinary characters
in.wordChars ('!', '/'); // ASCII range 33 to 47
in.wordChars (':', '@'); // ASCII range 58 to 64
in.wordChars ('[', '`'); // ASCII range 91 to 96
in.wordChars ('{', '~'); // ASCII range 123 to 126
String commandName = null;
String serviceName = null;
String className = null;
String classType = null;
String args = null;
// Create a state machine
int state = ServiceConfig.COMMAND_NAME;
// The apply() method on ParseNode starts the process of actually executing the
// desired action (suspend, load, etc)
ParseNode result = null;
while (in.nextToken () != StreamTokenizer.TT_EOF)
{
switch (state)
{
case ServiceConfig.COMMAND_NAME:
if (in.ttype == StreamTokenizer.TT_WORD) {
commandName = in.sval;
// This is a hack, but it should work until CUP is easier
// to deal with when multiple parsers are needed
if (commandName.equals("load"))
result = new AddServiceObjectNode(0);
else
if (commandName.equals("remove"))
result = new RemoveNode(0);
else
if (commandName.equals("suspend"))
result = new SuspendNode(0);
else
if (commandName.equals("resume"))
result = new ResumeNode(0);
else
throw new IOException ("COMMAND NAME missing or invalid: " + commandName);
ACE.DEBUG("Command node type: " + ((Object)result).getClass().getName());
} else
throw new IOException ("Illegal COMMAND NAME argument in file " + ServiceConfig.serviceConfigFile_);
state = ServiceConfig.SERVICE_NAME;
break;
case ServiceConfig.SERVICE_NAME:
if (in.ttype == StreamTokenizer.TT_WORD)
serviceName = in.sval;
else
throw new IOException ("Illegal SERVICE NAME argument in file " + ServiceConfig.serviceConfigFile_);
if (!commandName.equals("load")) {
result.init(serviceName);
result.apply();
in.whitespaceChars (' ', ' ');
state = ServiceConfig.SERVICE_NAME;
} else
state = ServiceConfig.CLASS_NAME;
break;
case ServiceConfig.CLASS_NAME:
if (in.ttype == StreamTokenizer.TT_WORD)
className = in.sval;
else
throw new IOException ("Illegal CLASS NAME argument in file " + ServiceConfig.serviceConfigFile_);
state = ServiceConfig.CLASS_TYPE;
break;
case ServiceConfig.CLASS_TYPE:
if (in.ttype == StreamTokenizer.TT_WORD)
classType = in.sval;
else
throw new IOException ("Illegal CLASS TYPE argument in file " + ServiceConfig.serviceConfigFile_);
state = ServiceConfig.ARGS;
// Set space to be an ordinary character to allow
// arguments to be parsed in
in.wordChars (' ', ' ');
break;
case ServiceConfig.ARGS:
ACE.DEBUG("Processing arguments");
args = new String("");
if (in.ttype == StreamTokenizer.TT_WORD)
{
args = in.sval;
// If just two double quotes, there are no args
if (args.length() == 2) {
args = new String("");
} else
args = args.substring(1, args.length() - 1);
}
// Quick hack until more parsing necessary -- set the needed data
((AddServiceObjectNode)result).init(serviceName, className, false);
((AddServiceObjectNode)result).params(args);
result.apply();
state = ServiceConfig.SERVICE_NAME;
// Set space back to whitespace-character to extract the
// next token
in.whitespaceChars (' ', ' ');
break;
default:
throw new IOException ("Illegal argument in file " + ServiceConfig.serviceConfigFile_);
}
}
return 0;
}
/** Parses the svc.conf file, treating it as a C++ ACE svc.conf file.
* This will involve attempts to infer the class name from
* the service initializer path, and isn't very accurate.
*/
public static int loadOldConfigFile ()
{
parser ps = new parser();
try {
ps.parse();
} catch (Exception e) {
ACE.ERROR("Error: " + e);
return -1;
}
return 0;
}
/**
* This is called when apply() is called on AddServiceObjectNodes. Similar
* methods could be developed for later data types (AddStreamNode, etc). This
* tries to load the ServiceObject and its classes. When trying to find info
* from the C++ files, this generates possible file paths.
*/
public static int initialize (AddServiceObjectNode son)
{
Class c = null;
if (ServiceConfig.oldConfigFormat_) {
// Generate a lot of possible file locations and names
ClassNameGenerator cng = new ClassNameGenerator(son.locator());
String attempt = null;
boolean ready = false;
// Try to load the class based on the names we can infer from
// the C++ svc.conf line
while ((cng.hasMoreElements()) && (!ready)) {
try {
attempt = (String)cng.nextElement();
c = loader_.loadClass(attempt, true);
ready = true;
} catch (ClassNotFoundException e) { }
}
// Couldn't find the class
if (!ready) {
ACE.ERROR("Can't find class with locator: " + son.locator());
return -1;
}
} else {
try {
c = loader_.loadClass(son.locator(), true);
} catch (ClassNotFoundException e) {
ACE.ERROR("Can't find class with locator: " + son.locator());
return 01;
}
}
try {
Object service = c.newInstance();
// Can't cast this to a ServiceObject, even though it will look just
// like one -- Java puts things loaded with a non-standard class loader
// in their own name space. The ServiceObjectRecord is a wrapper that
// gets around this by using reflection.
ServiceObjectRecord svcObjRec = new ServiceObjectRecord(service, son.name());
// Split the argument array up into smaller pieces
String [] argArray = OS.createStringArray (son.params(), " ");
// Initialize the service -- start it running
svcObjRec.init(argArray);
// Put it in the service repository
svcRep_.insert((ServiceRecord)svcObjRec);
} catch (IllegalAccessException e) {
ACE.ERROR("Error " + e);
return -1;
} catch (InstantiationException e) {
ACE.ERROR("Error " + e);
return -1;
}
return 0;
}
// Set by command line options
private static boolean oldConfigFormat_ = false;
private static boolean beADaemon_ = false;
private static boolean debug_ = false;
private static boolean noDefaults_ = false;
public static String serviceConfigFile_ = "svc.conf";
private static ServiceRepository svcRep_ = null;
private static ServiceLoader loader_ = null;
// States for the state-machine used in parsing the config file
private final static int SERVICE_NAME = 0;
private final static int CLASS_NAME = 1;
private final static int CLASS_TYPE = 2;
private final static int ARGS = 3;
private final static int COMMAND_NAME = 4;
}