/*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version. You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.aitools.programd;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.aitools.programd.graph.Graphmapper;
import org.aitools.programd.graph.Match;
import org.aitools.programd.interfaces.ConsoleStreamAppender;
import org.aitools.programd.interpreter.Interpreter;
import org.aitools.programd.logging.ChatLogEvent;
import org.aitools.programd.parser.BotsConfigurationFileParser;
import org.aitools.programd.parser.TemplateParser;
import org.aitools.programd.predicates.PredicateManager;
import org.aitools.programd.processor.aiml.AIMLProcessorRegistry;
import org.aitools.programd.util.AIMLWatcher;
import org.aitools.programd.util.Heart;
import org.aitools.programd.util.InputNormalizer;
import org.aitools.programd.util.ManagedProcesses;
import org.aitools.programd.util.NoMatchException;
import org.aitools.programd.util.Pulse;
import org.aitools.util.Classes;
import org.aitools.util.JDKLogHandler;
import org.aitools.util.UnspecifiedParameterError;
import org.aitools.util.db.DBConnectionManager;
import org.aitools.util.resource.Filesystem;
import org.aitools.util.resource.URLTools;
import org.aitools.util.runtime.DeveloperError;
import org.aitools.util.runtime.Errors;
import org.aitools.util.runtime.UserError;
import org.aitools.util.runtime.UserSystem;
import org.aitools.util.xml.JDOM;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jdom.Document;
/**
* The "core" of Program D, independent of any user interfaces.
*
* @author <a href="mailto:noel@aitools.org">Noel Bush</a>
*/
public class Core {
/** Possible values for status. */
public static enum Status {
/** The Core has not yet started. */
NOT_STARTED,
/** The Core has been properly initialized (internal, by constructor). */
INITIALIZED,
/** The Core has been properly set up (external, by user). */
READY,
/** The Core has shut down. */
SHUT_DOWN,
/** The Core has crashed. */
CRASHED
}
class UncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
/**
* Causes the Core to fail, with information about the exception.
*
* @see java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.lang.Thread, java.lang.Throwable)
*/
@Override
public void uncaughtException(Thread t, Throwable e) {
System.err.println(String.format("Uncaught exception \"%s\" in thread \"%s\".", Errors.describe(e), t.getName()));
if (Core.this._settings.printStackTraceOnUncaughtExceptions()) {
e.printStackTrace(System.err);
}
}
}
/** The namespace URI of the plugin configuration. */
public static final String PLUGIN_CONFIG_NS_URI = "http://aitools.org/programd/4.7/plugins";
/** The Settings. */
protected CoreSettings _settings;
/** The base URL. */
private URL _baseURL;
/** The Graphmapper. */
private Graphmapper _graphmapper;
/** The PredicateMaster. */
private PredicateManager _predicateManager;
/** The bots. */
private Bots _bots;
/** The processes that are managed by the core. */
private ManagedProcesses _processes;
/** The AIML processor registry. */
private AIMLProcessorRegistry _aimlProcessorRegistry;
/** An AIMLWatcher. */
private AIMLWatcher _aimlWatcher;
/** An interpreter. */
private Interpreter _interpreter;
/** The database connection manager (only initialized if database is used). */
private DBConnectionManager _dbConnectionManager;
/** The logger for the Core. */
private Logger _logger = LogManager.getLogger("programd");
/** The log where match info will be recorded. */
protected Logger _matchLogger = Logger.getLogger("programd.matching");
/** Name of the local host. */
private String _hostname;
/** A heart. */
private Heart _heart;
/** The plugin config. */
private Document _pluginConfig;
/** The value to return when a predicate is empty. */
private String _predicateEmptyDefault;
/** The time that the Multiplexor started operation. */
protected long _startTime = System.currentTimeMillis();
/** A counter for tracking the number of responses produced. */
protected long _responseCount = 0;
/** The total response time. */
protected long _totalTime = 0;
/** A counter for tracking average response time. */
protected float _avgResponseTime = 0;
/** The status of the Core. */
private Status _status = Status.NOT_STARTED;
/** A general-purpose map for storing all manner of objects (by AIML processors and the like). */
private Map<String, Map<String, Object>> classStorage = new HashMap<String, Map<String, Object>>();
/**
* Initializes a new Core object with default settings and the given base URL.
*
* @param base the base URL to use
*/
public Core(URL base) {
this.setup(base);
Filesystem.setRootPath(Filesystem.getWorkingDirectory());
this._settings = new ProgrammaticCoreSettings();
this._status = Status.INITIALIZED;
this.start();
}
/**
* Initializes a new Core object with the given CoreSettings object and the given base URL.
*
* @param base the base URL to use
* @param settings the settings to use
*/
public Core(URL base, CoreSettings settings) {
this.setup(base);
this._settings = settings;
Filesystem.setRootPath(this._baseURL);
this._status = Status.INITIALIZED;
this.start();
}
/**
* Initializes a new Core object with the settings from the given file and the given base URL.
*
* @param base the base URL to use
* @param settings the path to the file with settings
*/
public Core(URL base, URL settings) {
this.setup(base);
Filesystem.setRootPath(URLTools.getParent(this._baseURL));
this._settings = new XMLCoreSettings(settings, this._logger);
this._status = Status.INITIALIZED;
this.start();
}
/**
* Adds the given bot to the core.
*
* @param bot
*/
public void addBot(Bot bot) {
this._bots.put(bot.getID(), bot);
}
/**
* Notes the given Throwable and advises that the Core may no longer be stable.
*
* @param description the description of the Throwable
* @param t the thread in which the Throwable was thrown
* @param e the Throwable to log
*/
public void alert(String description, Thread t, Throwable e) {
String throwableDescription = e.getClass().getSimpleName() + " in thread \"" + t.getName() + "\"";
if (e.getMessage() != null) {
throwableDescription += ": " + e.getMessage();
}
else {
throwableDescription += ".";
}
this._logger.error("Core may no longer be stable due to " + description + ":\n" + throwableDescription);
if (this._settings.printStackTraceOnUncaughtExceptions()) {
if (e instanceof UserError || e instanceof DeveloperError) {
e.getCause().printStackTrace(System.err);
}
else {
e.printStackTrace(System.err);
}
}
}
/**
* Notes the given Throwable and advises that the Core may no longer be stable.
*
* @param description the description of the Throwable
* @param e the Throwable to log
*/
public void alert(String description, Throwable e) {
this.alert(description, Thread.currentThread(), e);
}
/**
* Notes the given Throwable and advises that the Core may no longer be stable.
*
* @param t the thread in which the Throwable was thrown
* @param e the Throwable to log
*/
public void alert(Thread t, Throwable e) {
this.alert(e.getClass().getSimpleName(), t, e);
}
/**
* Notes the given Throwable and advises that the Core may no longer be stable.
*
* @param e the Throwable to log
*/
public void alert(Throwable e) {
this.alert(e.getClass().getSimpleName(), Thread.currentThread(), e);
}
/**
* Returns the average response time.
*
* @return the average response time
*/
public float averageResponseTime() {
return this._avgResponseTime;
}
/**
* @return the AIML processor registry.
*/
public AIMLProcessorRegistry getAIMLProcessorRegistry() {
return this._aimlProcessorRegistry;
}
/**
* @return the AIMLWatcher
*/
public AIMLWatcher getAIMLWatcher() {
if (this._aimlWatcher != null) {
return this._aimlWatcher;
}
throw new NullPointerException("The Core's AIMLWatcher object has not yet been initialized!");
}
/**
* @return the base URL
*/
public URL getBaseURL() {
return this._baseURL;
}
/**
* @param id the id of the bot desired
* @return the requested bot
*/
public Bot getBot(String id) {
return this._bots.get(id);
}
/**
* @return the object that manages information about all bots
*/
public Bots getBots() {
if (this._bots != null) {
return this._bots;
}
throw new NullPointerException("The Core's Bots object has not yet been initialized!");
}
/**
* Returns a database connection from a pooling data source.
*
* @return the database connection
*/
public Connection getDBConnection() {
if (this._dbConnectionManager == null) {
this._dbConnectionManager =
new DBConnectionManager(this._settings.getDatabaseDriver(),
this._settings.getDatabaseURI(),
this._settings.getDatabaseUsername(),
this._settings.getDatabasePassword(),
this._settings.getDatabaseMinIdle(),
this._settings.getDatabaseMaxActive());
}
return this._dbConnectionManager.getDBConnection();
}
/**
* @return the Graphmapper
*/
public Graphmapper getGraphmapper() {
if (this._graphmapper != null) {
return this._graphmapper;
}
throw new NullPointerException("The Core's Graphmapper object has not yet been initialized!");
}
/**
* @return the local hostname
*/
public String getHostname() {
return this._hostname;
}
/**
* <p>
* Produces a response to an "internal" input sentence -- i.e., an input that has been produced by a
* <code>srai</code>.
* </p>
* <p>
* This method takes an already-existing <code>TemplateParser</code>, <i>doesn't </i> take a <code>Responder</code>,
* and assumes that the inputs have already been normalized.
* </p>
*
* @param input the input sentence
* @param userid the userid requesting the response
* @param botid the botid from which to get the response
* @param parser the parser object to update when generating the response
* @return the response
*/
@SuppressWarnings("boxing")
public String getInternalResponse(String input, String userid, String botid, TemplateParser parser) {
// Get the requested bot.
Bot bot = this._bots.get(botid);
String _input = input;
parser.addInput(_input);
// Ready the that and topic predicates for constructing the match path.
List<String> thatSentences = bot.sentenceSplit(this._predicateManager.get("that", 1, userid, botid));
String that = InputNormalizer.patternFitIgnoreCase(thatSentences.get(thatSentences.size() - 1));
if ("".equals(that) || that.equals(this._predicateEmptyDefault)) {
that = "*";
}
parser.addThat(that);
String topic = this._predicateManager.get("topic", userid, botid);
if ("".equals(topic) || topic.equals(this._predicateEmptyDefault)) {
topic = "*";
}
parser.addTopic(topic);
// Verify we've been tracking thats and topics correctly.
List<String> inputs = parser.getInputs();
List<String> thats = parser.getThats();
List<String> topics = parser.getTopics();
int stackSize = inputs.size();
assert stackSize == thats.size() && thats.size() == topics.size() : String.format("%d inputs, %d thats, %d topics",
stackSize, thats.size(), topics.size());
// Check for some simple kinds of infinite loops.
if (stackSize > 1) {
for (int lookback = stackSize - 2; lookback > -1; lookback--) {
String comparisonInput = inputs.get(lookback);
String comparisonThat = thats.get(lookback);
String comparisonTopic = topics.get(lookback);
String infiniteLoopInput = parser.getCore().getSettings().getInfiniteLoopInput();
if (that.equalsIgnoreCase(comparisonThat) && topic.equalsIgnoreCase(comparisonTopic)) {
if (_input.equalsIgnoreCase(infiniteLoopInput)) {
this._matchLogger.error("Unrecoverable infinite loop.");
return "";
}
if (_input.equalsIgnoreCase(comparisonInput)) {
_input = infiniteLoopInput;
inputs.set(stackSize - 1, infiniteLoopInput);
this._matchLogger.warn(String.format("Infinite loop detected; substituting \"%s\".", infiniteLoopInput));
}
}
}
}
return this.getMatchResult(_input, that, topic, userid, botid, parser);
}
/**
* @return the active JavaScript interpreter
*/
public Interpreter getInterpreter() {
if (this._interpreter != null) {
return this._interpreter;
}
throw new NullPointerException("The Core's Interpreter object has not yet been initialized!");
}
/**
* @return the logger
*/
public Logger getLogger() {
return this._logger;
}
/**
* @return the managed processes
*/
public ManagedProcesses getManagedProcesses() {
return this._processes;
}
/**
* Gets the match result from the Graphmaster.
*
* @param input the input to match
* @param that the current that value
* @param topic the current topic value
* @param userid the userid for whom to perform the match
* @param botid the botid for whom to perform the match
* @param parser the parser to use
* @return the match result
*/
protected String getMatchResult(String input, String that, String topic, String userid, String botid,
TemplateParser parser) {
// Show the input path.
if (this._matchLogger.isDebugEnabled()) {
this._matchLogger.debug(String.format("[INPUT (%s)] %s:%s:%s:%s", userid, input, that, topic, botid));
}
Match match = null;
try {
match = this._graphmapper.match(InputNormalizer.patternFitIgnoreCase(input), that, topic, botid);
}
catch (NoMatchException e) {
this._logger.warn(e.getMessage());
return "";
}
if (match == null) {
this._logger.warn(String.format("No match found for input \"%s\".", input));
return "";
}
if (this._matchLogger.isDebugEnabled()) {
this._matchLogger.debug(String.format("[MATCH (%s)] %s (\"%s\")", userid, match.getPath(), match.getFileNames()));
}
parser.addMatch(match);
String template = match.getTemplate();
String reply = null;
try {
reply = parser.processResponse(template, match.getFileNames().get(0));
}
catch (Throwable e) {
// Log the error message.
this._logger.error(String.format("Error while processing response: \"%s\"", Errors.describe(e)), e);
// Set response to empty string.
return "";
}
return reply;
}
/*
* All of these "get" methods throw a NullPointerException if the item has not yet been initialized, to avoid
* accidents.
*/
/**
* @return the plugin config
*/
public Document getPluginConfig() {
return this._pluginConfig;
}
/**
* @return the PredicateMaster
*/
public PredicateManager getPredicateMaster() {
if (this._predicateManager != null) {
return this._predicateManager;
}
throw new NullPointerException("The Core's PredicateMaster object has not yet been initialized!");
}
/**
* Gets the list of replies to some input sentences. Assumes that the sentences have already had all necessary
* pre-processing and substitutions performed.
*
* @param sentenceList the input sentences
* @param userid the userid requesting the replies
* @param botid
* @return the list of replies to the input sentences
*/
@SuppressWarnings("boxing")
protected List<String> getReplies(List<String> sentenceList, String userid, String botid) {
if (sentenceList == null) {
return null;
}
// All replies will be assembled in this ArrayList.
List<String> replies = Collections.checkedList(new ArrayList<String>(sentenceList.size()), String.class);
// Get the requested bot.
Bot bot = this._bots.get(botid);
// Ready the that and topic predicates for constructing the match path.
List<String> thatSentences = bot.sentenceSplit(this._predicateManager.get("that", 1, userid, botid));
String that = null;
if (thatSentences.size() > 0) {
that = InputNormalizer.patternFitIgnoreCase(thatSentences.get(thatSentences.size() - 1));
}
if (that == null || "".equals(that) || that.equals(this._predicateEmptyDefault)) {
that = "*";
}
String topic = InputNormalizer.patternFitIgnoreCase(this._predicateManager.get("topic", userid, botid));
if ("".equals(topic) || topic.equals(this._predicateEmptyDefault)) {
topic = "*";
}
// We might use this to track matching statistics.
long time = 0;
// Mark the time just before matching starts.
time = System.currentTimeMillis();
// Get a reply for each sentence.
for (String sentence : sentenceList) {
replies.add(this.getReply(sentence, that, topic, userid, botid));
}
// Increment the (static) response count.
this._responseCount++;
// Produce statistics about the response time.
// Mark the time that processing is finished.
time = System.currentTimeMillis() - time;
// Calculate the average response time.
this._totalTime += time;
this._avgResponseTime = (float) this._totalTime / (float) this._responseCount;
if (this._matchLogger.isDebugEnabled()) {
this._matchLogger.debug(String.format("Response %d in %dms. (Average: %.2fms)", this._responseCount, time,
this._avgResponseTime));
}
// Invoke targeting if appropriate.
/*
* if (responseCount % TARGET_SKIP == 0) { if (USE_TARGETING) { Graphmaster.checkpoint(); } }
*/
// If no replies, return an empty string.
if (replies.size() == 0) {
replies.add("");
}
return replies;
}
/**
* Gets a reply to an input. Assumes that the input has already had all necessary substitutions and pre-processing
* performed, and that the input is a single sentence.
*
* @param input the input sentence
* @param that the input that value
* @param topic the input topic value
* @param userid the userid requesting the reply
* @param botid
* @return the reply to the input sentence
*/
protected String getReply(String input, String that, String topic, String userid, String botid) {
// Push the input onto the <input/> stack.
this._predicateManager.push("input", input, userid, botid);
// Create a new TemplateParser.
TemplateParser parser = new TemplateParser(input, that, topic, userid, botid, this);
String reply = this.getMatchResult(input, that, topic, userid, botid, parser);
if (reply == null) {
this._logger.error("getMatchReply generated a null reply!", new NullPointerException("Null reply."));
return "";
}
// Push the reply onto the <that/> stack.
this._predicateManager.push("that", reply, userid, botid);
return reply;
}
/**
* Returns the response to an input.
*
* @param input the "non-internal" (possibly multi-sentence, non-substituted) input
* @param userid the userid for whom the response will be generated
* @param botid the botid from which to get the response
* @return the response
*/
public synchronized String getResponse(String input, String userid, String botid) {
if (this._status == Status.READY) {
// Get the specified bot object.
Bot bot = this._bots.get(botid);
// Split sentences (after performing substitutions).
List<String> sentenceList = bot.sentenceSplit(bot.applyInputSubstitutions(input));
// Get the replies.
List<String> replies = this.getReplies(sentenceList, userid, botid);
if (replies == null) {
return null;
}
// Start by assuming an empty response.
StringBuilder responseBuffer = new StringBuilder("");
// Append each reply to the response.
for (String reply : replies) {
responseBuffer.append(reply);
}
String response = responseBuffer.toString();
// Log the response.
this.logResponse(input, response, userid, botid);
// Return the response (may be just ""!)
return response;
}
// otherwise...
// throw new DeveloperError("Check that the Core is running before sending it messages.", new
// CoreNotReadyException());
return null;
}
/**
* @return the settings for this core
*/
public CoreSettings getSettings() {
if (this._settings != null) {
return this._settings;
}
throw new NullPointerException("The Core's CoreSettings object has not yet been initialized!");
}
/**
* @return the status of the Core
*/
public Status getStatus() {
return this._status;
}
/**
* Gets an object from the class storage, using the given classname to look up the map for the class, then the given
* key to retrieve the object. If the object is not found, then the given defaultObject is stored with the appropriate
* key, so it will be there next time.
*
* @param <T> the type of object to retrieve
* @param classname the classname from whose map to retrieve
* @param key the key to retrieve
* @param defaultObject default object to use if none is found
* @return the object associated with this key
*/
@SuppressWarnings("unchecked")
public <T> T getStoredObject(String classname, String key, T defaultObject) {
Map<String, Object> storageMap = this.classStorage.get(classname);
if (storageMap == null) {
storageMap = new HashMap<String, Object>();
this.classStorage.put(classname, storageMap);
}
Object object = storageMap.get(key);
if (object != null) {
return (T) object;
}
storageMap.put(key, defaultObject);
return defaultObject;
}
/**
* Loads the given path for the given botid.
*
* @param path
* @param botid
*/
public void load(URL path, String botid) {
this._graphmapper.load(path, botid);
}
/**
* Loads bot(s) from the indicated config file path.
*
* @param path the config file path
*/
public void loadBotConfig(URL path) {
if (this._settings.useAIMLWatcher()) {
this._logger.debug("Suspending AIMLWatcher.");
this._aimlWatcher.stop();
}
new BotsConfigurationFileParser(this).parse(path);
if (this._settings.useAIMLWatcher()) {
this._logger.debug("Restarting AIMLWatcher.");
this._aimlWatcher.start();
}
}
/**
* Logs a response to the chat log.
*
* @param input the input that produced the response
* @param response the response
* @param userid the userid for whom the response was produced
* @param botid the botid that produced the response
*/
protected void logResponse(String input, String response, String userid, String botid) {
/*
* NOTA BENE: This is a very specific workaround for a problem with the log4j JDBCAppender. It appears that the
* appender fails to maintain the database connection after some period of time, and thus stops working. Here, we
* catch the exception thrown in this case, and force log4j to reinitialize the appender. This is horribly specific
* and should go away as soon as possible. - 2006-03-29, NB
*/
try {
this._logger.callAppenders(new ChatLogEvent(botid, userid, input, response));
}
catch (Exception e) {
this._logger
.error(
"A known bug with log4j has been encountered. Attempting to reset logging configuration. This may or may not work.",
e);
LogManager.resetConfiguration();
}
}
/**
* Processes the given input using default values for userid (the hostname), botid (the first available bot), and no
* responder. The result is not returned. This method is mostly useful for a simple test of the Core.
*
* @param input the input to send
*/
public synchronized void processResponse(String input) {
if (this._status == Status.READY) {
Bot bot = this._bots.getABot();
if (bot != null) {
this.getResponse(input, this._hostname, bot.getID());
return;
}
this._logger.warn("No bot available to process response!");
return;
}
// throw new DeveloperError("Check that the Core is ready before sending it messages.", new
// CoreNotReadyException());
}
/**
* Returns the number of queries per hour.
*
* @return the number of queries per hour
*/
public float queriesPerHour() {
return this._responseCount / ((System.currentTimeMillis() - this._startTime) / 3600000.00f);
}
/**
* Reloads the given path for the given botid.
*
* @param path
*/
public void reload(URL path) {
Set<Bot> bots = new HashSet<Bot>();
for (Bot bot : this._bots.values()) {
if (bot.getLoadedFilesMap().containsKey(path)) {
bots.add(bot);
}
}
// First unload all,
for (Bot bot : bots) {
this._graphmapper.unload(path, bot);
}
// then reload all.
for (Bot bot : bots) {
this._graphmapper.load(path, bot.getID());
}
}
/**
* Removes the given bot from the core, if it exists.
*
* @param bot
* @return the bot if it was there, null if not
*/
public Bot removeBot(Bot bot) {
return this._bots.remove(bot);
}
private void setup(URL base) {
this._baseURL = base;
}
protected void setupInterpreter() {
if (this._settings.allowJavaScript()) {
if (this._settings.getJavascriptInterpreterClassname() == null) {
this._logger.error(new UnspecifiedParameterError("javascript-interpreter.classname"));
}
String javascriptInterpreterClassname = this._settings.getJavascriptInterpreterClassname();
if ("".equals(javascriptInterpreterClassname)) {
this._logger.error(new UnspecifiedParameterError("javascript-interpreter.classname"));
}
this._logger.info("Initializing " + javascriptInterpreterClassname + ".");
try {
this._interpreter = (Interpreter) Class.forName(javascriptInterpreterClassname).newInstance();
}
catch (Exception e) {
this._logger.error("Error while creating new instance of JavaScript interpreter.", e);
}
}
else {
this._logger.info("JavaScript interpreter not started.");
}
}
/**
* Performs all necessary shutdown tasks. Shuts down the Graphmaster and all ManagedProcesses.
*/
public void shutdown() {
this._logger.info("Program D is shutting down.");
this._processes.shutdownAll();
this._predicateManager.saveAll();
this._logger.info("Shutdown complete.");
this._status = Status.SHUT_DOWN;
}
/**
* Initializes and starts up the Core.
*/
protected void start() {
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler());
// Use the stdout and stderr appenders in a special way, if they are defined.
ConsoleStreamAppender stdOutAppender = (ConsoleStreamAppender) Logger.getLogger("programd").getAppender("stdout");
if (stdOutAppender != null) {
if (!stdOutAppender.isWriterSet()) {
stdOutAppender.setWriter(new OutputStreamWriter(System.out));
}
}
ConsoleStreamAppender stdErrAppender = (ConsoleStreamAppender) Logger.getLogger("programd").getAppender("stderr");
if (stdErrAppender != null) {
if (!stdErrAppender.isWriterSet()) {
stdErrAppender.setWriter(new OutputStreamWriter(System.err));
}
}
// Set up an interception of calls to the JDK logging system and re-route to log4j.
JDKLogHandler.setupInterception();
this._logger.info(String.format("Base URL for Program D Core: \"%s\".", this._baseURL));
this._aimlProcessorRegistry = new AIMLProcessorRegistry();
this._graphmapper = Classes.getSubclassInstance(Graphmapper.class, this._settings.getGraphmapperImplementation(),
"Graphmapper implementation", this);
this._bots = new Bots();
this._processes = new ManagedProcesses(this);
// Get an instance of the settings-specified PredicateManager.
this._predicateManager = Classes.getSubclassInstance(PredicateManager.class,
this._settings.getPredicateManagerImplementation(), "PredicateManager", this);
// Get the hostname (used occasionally).
try {
this._hostname = InetAddress.getLocalHost().getHostName();
}
catch (UnknownHostException e) {
this._hostname = "unknown hostname";
}
// Load the plugin config.
URL pluginConfigURL = this._settings.getPluginConfigURL();
if (pluginConfigURL != null) {
pluginConfigURL = URLTools.contextualize(this._baseURL, pluginConfigURL);
try {
if (pluginConfigURL.openStream() != null) {
this._pluginConfig = JDOM.getDocument(pluginConfigURL, this._settings.getXmlCatalogPath(), this._logger);
}
}
catch (IOException e) {
// Don't load plugin config.
}
}
// TODO: Make this work even if the classes aren't in a jar.
Package pkg = Package.getPackage("org.aitools.programd");
this._logger.info(String.format("Starting %s version %s [%s].", pkg.getSpecificationTitle(),
pkg.getSpecificationVersion(), pkg.getImplementationVersion()));
this._logger.info(UserSystem.jvmDescription());
this._logger.info(UserSystem.osDescription());
this._logger.info(UserSystem.memoryReport());
this._logger.info("Predicates with no values defined will return: \"" + this._settings.getPredicateEmptyDefault()
+ "\".");
try {
// Create the AIMLWatcher if configured to do so.
if (this._settings.useAIMLWatcher()) {
this._aimlWatcher = new AIMLWatcher(this);
}
// Setup a JavaScript interpreter if supposed to.
this.setupInterpreter();
// Start the AIMLWatcher if configured to do so.
this.startWatcher();
this._logger.info("Starting up the Graphmaster.");
// Start loading bots.
URL botConfigURL = this._settings.getBotConfigURL();
if (botConfigURL != null) {
this.loadBotConfig(botConfigURL);
}
else {
this._logger.warn("No bot config URL specified; no bots will be loaded.");
}
// Request garbage collection.
System.gc();
this._logger.info(UserSystem.memoryReport());
// Start the heart, if enabled.
this.startHeart();
}
catch (DeveloperError e) {
this.alert("developer error", e);
}
catch (UserError e) {
this.alert("user error", e);
}
catch (RuntimeException e) {
this.alert("unforeseen runtime exception", e);
}
catch (Throwable e) {
this.alert("unforeseen problem", e);
}
// Set the status indicator.
this._status = Status.READY;
// Exit immediately if configured to do so (for timing purposes).
if (this._settings.exitImmediatelyOnStartup()) {
this.shutdown();
}
}
protected void startHeart() {
if (this._settings.heartEnabled()) {
this._heart = new Heart(this._settings.getHeartPulseRate());
String pulseImplementation = this._settings.getPulseImplementation();
if ("".equals(pulseImplementation)) {
this._logger.error(new UnspecifiedParameterError("pulse.implementation"));
}
else {
this._heart.addPulse(Classes.getSubclassInstance(Pulse.class, pulseImplementation, "Pulse", this));
this._heart.start();
this._logger.info("Heart started.");
}
}
}
protected void startWatcher() {
if (this._settings.useAIMLWatcher()) {
this._aimlWatcher.start();
this._logger.info("The AIML Watcher is active.");
}
else {
this._logger.info("The AIML Watcher is not active.");
}
}
/**
* Unloads a bot with the given id.
*
* @param id the bot to unload
*/
public void unload(String id) {
if (!this._bots.containsKey(id)) {
this._logger.warn("Bot \"" + id + "\" is not loaded; cannot unload.");
return;
}
Bot bot = this._bots.get(id);
for (URL path : bot.getLoadedFilesMap().keySet()) {
this._graphmapper.unload(path, bot);
}
this._bots.remove(id);
this._logger.info("Bot \"" + id + "\" has been unloaded.");
}
/**
* Unloads the given path for the given botid.
*
* @param path
* @param botid
*/
public void unload(URL path, String botid) {
this._graphmapper.unload(path, this.getBot(botid));
}
}