com.basetechnology.s0.agentserver.AgentServer.java Source code

Java tutorial

Introduction

Here is the source code for com.basetechnology.s0.agentserver.AgentServer.java

Source

/**
 * Copyright 2012 John W. Krupansky d/b/a Base Technology
 * 
 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *     
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.basetechnology.s0.agentserver;

import java.io.IOException;
import java.text.ParseException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.log4j.Logger;
import org.json.JSONException;
import org.json.JSONObject;

import com.basetechnology.s0.agentserver.appserver.AgentAppServer;
import com.basetechnology.s0.agentserver.config.AgentServerConfig;
import com.basetechnology.s0.agentserver.config.AgentServerProperties;
import com.basetechnology.s0.agentserver.config.AgentServerWebAccessConfig;
import com.basetechnology.s0.agentserver.mailaccessmanager.MailAccessManager;
import com.basetechnology.s0.agentserver.persistence.Persistence;
import com.basetechnology.s0.agentserver.persistence.persistentfile.PersistentFileException;
import com.basetechnology.s0.agentserver.scheduler.AgentScheduler;
import com.basetechnology.s0.agentserver.script.intermediate.Symbol;
import com.basetechnology.s0.agentserver.script.intermediate.SymbolException;
import com.basetechnology.s0.agentserver.script.intermediate.SymbolManager;
import com.basetechnology.s0.agentserver.script.intermediate.SymbolTable;
import com.basetechnology.s0.agentserver.script.intermediate.SymbolValues;
import com.basetechnology.s0.agentserver.script.parser.ParserException;
import com.basetechnology.s0.agentserver.script.parser.tokenizer.TokenizerException;
import com.basetechnology.s0.agentserver.script.runtime.value.Value;
import com.basetechnology.s0.agentserver.util.DateUtils;
import com.basetechnology.s0.agentserver.util.ListMap;
import com.basetechnology.s0.agentserver.util.NameValueList;
import com.basetechnology.s0.agentserver.webaccessmanager.WebAccessManager;
import com.basetechnology.s0.agentserver.webaccessmanager.WebPage;
import com.basetechnology.s0.agentserver.webaccessmanager.WebSiteAccessConfig;

public class AgentServer {
    static final Logger log = Logger.getLogger(AgentServer.class);

    public AgentAppServer agentAppServer;
    public static AgentServer singleton = null;
    public List<String> fieldTypes = Arrays.asList("string", "int", "float", "money", "option", "choice",
            "multi_choice", "date", "location", "text", "help");
    public long startTime = 0;
    public AgentScheduler agentScheduler;

    public Persistence persistence;
    public AgentServerConfig config;
    public NameValueList<User> users;
    public NameValueList<AgentDefinitionList> agentDefinitions;
    public NameValueList<AgentInstanceList> agentInstances;
    public AgentServerProperties agentServerProperties;
    public AgentServerWebAccessConfig webAccessConfig;
    public WebSiteAccessConfig webSiteAccessConfig;
    public WebAccessManager webAccessManager;
    public MailAccessManager mailAccessManager;

    public AgentServer(AgentAppServer agentAppServer) throws RuntimeException, AgentServerException, IOException,
            InterruptedException, PersistentFileException, ParseException, TokenizerException, ParserException {
        this(agentAppServer, true);
    }

    public AgentServer(AgentAppServer agentAppServer, boolean start)
            throws RuntimeException, AgentServerException, IOException, InterruptedException,
            PersistentFileException, ParseException, TokenizerException, ParserException {
        log.info("Creation of AgentServer object");

        // Link back to app server
        this.agentAppServer = agentAppServer;

        // Singleton access is now permitted
        singleton = this;

        // Start the agent server
        start(start);
    }

    public static AgentServer getSingleton() throws AgentServerException, InterruptedException, IOException,
            PersistentFileException, ParseException, TokenizerException, ParserException {
        if (singleton == null)
            return (singleton = new AgentServer(null, false));
        //throw new AgentServerException("Cannot access singleton of AgentServer until server is instantiated");
        else
            return singleton;
    }

    public AgentDefinition addAgentDefinition(AgentDefinition agentDefinition) throws AgentServerException {
        if (agentDefinition != null) {
            // Check if the user has any agents yet
            if (!agentDefinitions.containsKey(agentDefinition.user.id)) {
                // No, so create an empty agent table for user
                agentDefinitions.put(agentDefinition.user.id, new AgentDefinitionList());
            }

            // Get agent definition table for the user
            AgentDefinitionList usersAgentDefinitions = agentDefinitions.get(agentDefinition.user.id);

            // Store the new agent definition for the user
            usersAgentDefinitions.put(agentDefinition);

            // Persist the new agent definition
            persistence.put(agentDefinition);

        }

        // Return the agent definition itself
        return agentDefinition;
    }

    public AgentDefinition addAgentDefinition(String agentJsonString)
            throws SymbolException, RuntimeException, AgentServerException {
        if (agentJsonString == null || agentJsonString.trim().length() == 0)
            agentJsonString = "{}";
        try {
            return addAgentDefinition(new JSONObject(agentJsonString));
        } catch (JSONException e) {
            throw new AgentServerException("JSON parsing exception: " + e.getMessage());
        }
    }

    public AgentDefinition addAgentDefinition(JSONObject agentJson)
            throws SymbolException, RuntimeException, AgentServerException {
        return addAgentDefinition(null, agentJson);
    }

    public AgentDefinition addAgentDefinition(User user, JSONObject agentJson)
            throws SymbolException, RuntimeException, AgentServerException {
        // Parse the JSON for the agent definition
        AgentDefinition agentDefinition = AgentDefinition.fromJson(this, user, agentJson);

        // Add it to table of agent definitions
        addAgentDefinition(agentDefinition);

        // Return the new agent definition
        return agentDefinition;
        /*
            // If we have the user, ignore user from JSON
            if (user == null){
              String userId = agentJson.optString("user");
              if (userId == null || userId.trim().length() == 0)
                throw new AgentServerException("Agent definition user id ('user') is missing");
              user = getUser(userId);
              if (user == User.noUser)
                throw new AgentServerException("Agent definition user id does not exist: '" + userId + "'");
            }
            
            // Parse agent definition name
            String agentDefinitionName = agentJson.optString("name");
            if (agentDefinitionName == null || agentDefinitionName.trim().length() == 0)
              throw new AgentServerException("Agent definition name ('name') is missing");
            
            // Parse agent definition description
            String agentDescription = agentJson.optString("description");
            if (agentDescription == null || agentDescription.trim().length() == 0)
              agentDescription = "";
            log.info("Adding new agent definition named: " + agentDefinitionName + " for user: " + user.id);
                
            // TODO: Parse comment
                
            AgentDefinitionList agentMap = agentDefinitions.get(user.id);
            if (agentMap == null){
              agentMap = new AgentDefinitionList();
              agentDefinitions.add(user.id, agentMap);
            }
            
            // Check if named agent definition already exists
            if (agentMap.containsKey(agentDefinitionName))
              throw new AgentServerException("Agent definition name already exists: '" + agentDefinitionName + "'");
            
            AgentDefinition agent = null;
            
            SymbolManager symbolManager = new SymbolManager();
            
            String invalidParameterNames = "";
            String invalidDataSourceNames = "";
            String invalidEventNames = "";
            
            // Parse 'parameter' fields
            FieldList parameters = null;
            if (agentJson.has("parameters")){
              JSONArray parameterJson = agentJson.optJSONArray("parameters");
              parameters = new FieldList();
              int numparameter = parameterJson.length();
              for (int i = 0; i < numparameter; i++){
                JSONObject outputJson = parameterJson.optJSONObject(i);
                Field field = Field.fromJsonx(symbolManager.getSymbolTable("parameter"), outputJson);
                // TODO: give error for dup names
                parameters.add(field);
              }
            }
            
            // Parse inputs - data source references
            DataSourceReferenceList inputs = null;
            if (agentJson.has("inputs")){
              SymbolTable symbolTable = symbolManager.getSymbolTable("parameter_values");
              JSONArray inputsJson = agentJson.optJSONArray("inputs");
              inputs = new DataSourceReferenceList();
              int numInputs = inputsJson.length();
              for (int i = 0; i < numInputs; i++){
                Object element = inputsJson.opt(i);
                String dataSourceName = null;
                DataSourceReference dataSourceReference = null;
                if (element instanceof JSONObject){
                  // Ignore empty data sources
                  JSONObject elementJson = (JSONObject)element;
                  if (elementJson.length() == 0)
        continue;
            
                  // Parse the 'name' for data source
                  if (! elementJson.has("name"))
        throw new AgentServerException("Inputs JSON object is missing 'name' key");
                  dataSourceName = elementJson.optString("name");
                      
                  // Parse the 'data_source'
                  if (! elementJson.has("data_source"))
        throw new AgentServerException("Inputs JSON object is missing 'data_source' key");
                  String dataSourceDataSource = elementJson.optString("data_source");
                      
                  // Validate the data source reference
                  AgentDefinition dataSourceDefinition = agentMap.get(dataSourceDataSource);
                  if (dataSourceDefinition == null)
        throw new AgentServerException("Inputs JSON for data source name '" + dataSourceName + "' references data source '" + dataSourceDataSource + "' which is not a defined data source");
                      
                  // Parse the optional 'parameter_values'
                  SymbolValues parameterValues = null;
                  if (elementJson.has("parameter_values")){
        Object parametersObject = elementJson.opt("parameter_values");
        if (! (parametersObject instanceof JSONObject))
          throw new AgentServerException("Inputs parameters_values value expected JSON object but found " + parametersObject.getClass().getSimpleName());
        JSONObject parameterValuesJson = elementJson.optJSONObject("parameter_values");
        parameterValues = SymbolValues.fromJson(symbolTable, parameterValuesJson);
                  } else
        parameterValues = new SymbolValues("parameters");
            
                  // Validate that parameter value keys are all valid data source definition parameters
                  Map<String, Value> treeMap = new TreeMap<String, Value>();
                  for (Symbol symbol: parameterValues)
        treeMap.put(symbol.name, null);
                  invalidParameterNames = "";
                  for (String parameterName: treeMap.keySet())
        if (! dataSourceDefinition.parameters.containsKey(parameterName))
          invalidParameterNames += (invalidParameterNames.length() > 0 ? ", " : "") + parameterName;
                  if (invalidParameterNames.length() > 0)
        throw new AgentServerException("Invalid parameter field names for inputs name '" + dataSourceName + "' for data source '" + agentDefinitionName + "': " + invalidParameterNames);
            
                  // Construct the data source reference, including parameter values
                  dataSourceReference = new DataSourceReference(dataSourceName, dataSourceDefinition, parameterValues);
                } else if (element instanceof String){
                  String dataSourceDataSource = (String)element;
                  dataSourceName = dataSourceDataSource;
                      
                  // Validate the data source reference
                  AgentDefinition dataSourceDefinition = agentMap.get(dataSourceDataSource);
                  if (dataSourceDefinition == null)
        throw new AgentServerException("Inputs JSON references data source '" + dataSourceDataSource + "' which is not a defined data source");
            
                  // Construct the data source reference
                  dataSourceReference = new DataSourceReference(dataSourceName, dataSourceDefinition);
                } else
                  throw new AgentServerException("Expected JSON object or string for input data source, but encountered " + element.getClass().getSimpleName());
                    
                // Add this data source reference to the list
                inputs.add(dataSourceReference);
              }
            }
            
            // Parse 'timers' list
            NameValueList<AgentTimer> timers = new NameValueList<AgentTimer>();
            if (agentJson.has("timers")){
              JSONArray timersJson = agentJson.optJSONArray("timers");
              if (timersJson != null){
                int numScripts = timersJson.length();
                for (int i = 0; i < numScripts; i++){
                  // Get JSON for next timer
                  JSONObject timerJson = timersJson.optJSONObject(i);
                      
                  // Ignore empty timers
                  if (! timerJson.keys().hasNext())
        continue;
                      
                  AgentTimer timerDefinition = AgentTimer.fromJson(timerJson);
                  timers.put(timerDefinition.name, timerDefinition);
                }
              }
            }
            
            // Parse 'conditions' list
            NameValueList<AgentCondition> conditions = new NameValueList<AgentCondition>();
            if (agentJson.has("conditions")){
              JSONArray conditionsJson = agentJson.optJSONArray("conditions");
              if (conditionsJson != null){
                int numScripts = conditionsJson.length();
                for (int i = 0; i < numScripts; i++){
                  // Get JSON for next condition
                  JSONObject conditionJson = conditionsJson.optJSONObject(i);
                      
                  // Ignore empty conditions
                  if (! conditionJson.keys().hasNext())
        continue;
                      
                  AgentCondition conditionDefinition = AgentCondition.fromJson(conditionJson);
                  conditions.put(conditionDefinition.name, conditionDefinition);
                }
              }
            }
            
            // Parse 'scripts' list
            NameValueList<ScriptDefinition> scripts = new NameValueList<ScriptDefinition>();
            if (agentJson.has("scripts")){
              JSONArray scriptsJson = agentJson.optJSONArray("scripts");
              if (scriptsJson != null){
                int numScripts = scriptsJson.length();
                for (int i = 0; i < numScripts; i++){
                  // Get JSON for next script
                  JSONObject scriptJson = scriptsJson.optJSONObject(i);
                      
                  // Ignore empty scripts
                  if (! scriptJson.keys().hasNext())
        continue;
                      
                  ScriptDefinition scriptDefinition = ScriptDefinition.fromJson(scriptJson);
                  scripts.put(scriptDefinition.name, scriptDefinition);
                }
              }
            }
            
            // Parse notifications
            NameValueList<NotificationDefinition> notifications = new NameValueList<NotificationDefinition>();
            if (agentJson.has("notifications")){
              JSONArray notificationsJson = agentJson.optJSONArray("notifications");
              if (notificationsJson != null){
                int numScripts = notificationsJson.length();
                for (int i = 0; i < numScripts; i++){
                  // Get JSON for next notification
                  JSONObject notificationJson = notificationsJson.optJSONObject(i);
                      
                  // Ignore empty notifications
                  if (! notificationJson.keys().hasNext())
        continue;
                      
                  NotificationDefinition notificationDefinition = NotificationDefinition.fromJson(notificationJson);
                  notifications.put(notificationDefinition.name, notificationDefinition);
                }
              }
            }
            
            // Parse outputs
            FieldList outputsList = null;
            if (agentJson.has("outputs")){
              JSONArray outputsJson = agentJson.optJSONArray("outputs");
              outputsList = new FieldList();
              int numOutputs = outputsJson.length();
              for (int i = 0; i < numOutputs; i++){
                // Get next output field definition
                JSONObject outputJson = outputsJson.optJSONObject(i);
                    
                // Ignore empty field definitions
                if (outputJson.length() == 0)
                  continue;
                    
                // Parse the output field definition
                Field field = Field.fromJsonx(symbolManager.getSymbolTable("outputs"), outputJson);
            
                // TODO: give error for dup names
                    
                // Add parsed field to the outputs field list
                outputsList.add(field);
              }
            }
            
            // Parse 'scratchpad' fields
            FieldList scratchpad = null;
            if (agentJson.has("scratchpad")){
              JSONArray scratchpadJson = agentJson.optJSONArray("scratchpad");
              scratchpad = new FieldList();
              int numMemory = scratchpadJson.length();
              for (int i = 0; i < numMemory; i++){
                JSONObject fieldJson = scratchpadJson.optJSONObject(i);
                Field field = Field.fromJsonx(symbolManager.getSymbolTable("scratchpad"), fieldJson);
                // TODO: give error for dup names
                scratchpad.add(field);
              }
            }
            
            // Parse 'memory' fields
            FieldList memory = null;
            if (agentJson.has("memory")){
              JSONArray memoryJson = agentJson.optJSONArray("memory");
              memory = new FieldList();
              int numMemory = memoryJson.length();
              for (int i = 0; i < numMemory; i++){
                JSONObject fieldJson = memoryJson.optJSONObject(i);
                Field field = Field.fromJsonx(symbolManager.getSymbolTable("memory"), fieldJson);
                // TODO: give error for dup names
                memory.add(field);
              }
            }
            
            // Parse 'goals'
            List<Goal> goalsList = null;
            if (agentJson.has("goals")){
              JSONArray goalsJson = agentJson.optJSONArray("goals");
              goalsList = new ArrayList<Goal>();
              int numGoals = goalsJson.length();
              for (int i = 0; i < numGoals; i++){
                // Get next goal definition
                JSONObject goalJson = goalsJson.optJSONObject(i);
                    
                // Ignore empty goal definitions
                if (goalJson.length() == 0)
                  continue;
                    
                // Parse the goal definition
                Goal goal = Goal.fromJson(goalJson);
            
                // Add parsed goal to the goals list
                goalsList.add(goal);
              }
            }
            
            long reportingInterval = agentJson.optLong("reporting_interval", AgentDefinition.DEFAULT_REPORTING_INTERVAL);
            long triggerInterval = agentJson.optLong("trigger_interval", AgentDefinition.DEFAULT_TRIGGER_INTERVAL);
            
            String created = agentJson.optString("created", null);
            long timeCreated = -1;
            try {
              timeCreated = created != null ? DateUtils.parseRfcString(created): -1;
            } catch (ParseException e){
              throw new AgentServerException("Unable to parse created date ('" + created + "') - " + e.getMessage());
            }
            String modified = agentJson.optString("modified", null);
            long timeModified = -1;
            try {
              timeModified = modified != null ? DateUtils.parseRfcString(modified): -1;
            } catch (ParseException e){
              throw new AgentServerException("Unable to parse modified date ('" + modified + "') - " + e.getMessage());
            }
                
            // Validate keys
            JsonUtils.validateKeys(agentJson, "Agent definition", new ArrayList<String>(Arrays.asList(
                "user", "name", "definition", "description", "parameter_values", "trigger_interval", "reporting_interval",
                "enabled", "instantiated", "updated", "state",
                "status", "inputs_changed", "triggered", "outputs_changed")));
            
            if (invalidParameterNames.length() == 0){
                agent = new AgentDefinition(this, user, agentDefinitionName, agentDescription, parameters, inputs, timers, conditions, scripts, scratchpad, memory, notifications, outputsList, goalsList, triggerInterval, reportingInterval, timeCreated, timeModified);
                addAgentDefinition(agent);
            } else {
              throw new AgentServerException("Invalid field names for agent of class " + agentDefinitionName + ": " + invalidParameterNames);
            }
            
            // Return the created agent definition
            return agent;
            */
    }

    public AgentDefinition getAgentDefinition(User user, String agentDefinitionName) {
        AgentDefinitionList agentMap = agentDefinitions.get(user.id);
        if (agentMap == null)
            return null;
        else
            return agentMap.get(agentDefinitionName);
    }

    public void clearAgentDefinitions(String userId) throws AgentServerException {
        // Check if the user has any agents yet
        if (!agentDefinitions.containsKey(userId))
            // No, so nothing more to do
            return;

        // Get agent table for the user
        AgentDefinitionList usersAgents = agentDefinitions.get(userId);

        // Clear the user's agent list
        usersAgents.clear();
    }

    public void removeAgentDefinition(AgentDefinition agentDefinition) throws AgentServerException {
        String userId = agentDefinition.user.id;
        String agentName = agentDefinition.name;

        // Check if the user has any agents yet
        if (!agentDefinitions.containsKey(userId))
            throw new AgentServerException("Attempt to delete agent definition ('" + agentName + "') for a user ('"
                    + userId + "') that has no agents");

        // Get agent table for the user
        AgentDefinitionList usersAgents = agentDefinitions.get(userId);

        // Check if that agent exists for user
        if (!usersAgents.containsKey(agentName))
            throw new AgentServerException("Attempt to delete agent definition ('" + agentName
                    + "') that does not exist for user ('" + userId + "')");

        // Delete the named agent definition for the user
        usersAgents.remove(agentName);
    }

    public void removeAgentDefinition(String userId, String agentName) throws AgentServerException {
        // Check if the user has any agents yet
        if (!agentDefinitions.containsKey(userId))
            throw new AgentServerException("Attempt to delete agent definition ('" + agentName + "') for a user ('"
                    + userId + "') that has no agents");

        // Get agent table for the user
        AgentDefinitionList usersAgents = agentDefinitions.get(userId);

        // Check if that agent exists for user
        if (!usersAgents.containsKey(agentName))
            throw new AgentServerException("Attempt to delete agent definition ('" + agentName
                    + "') that does not exist for user ('" + userId + "')");

        // Delete the named agent definition for the user
        usersAgents.remove(agentName);
    }

    public AgentInstance addAgentInstance(AgentInstance agentInstance)
            throws AgentServerException, SymbolException, JSONException {
        // Get instance list for the user
        AgentInstanceList agentInstanceList = agentInstances.get(agentInstance.user.id);
        if (agentInstanceList == null) {
            // Need to do the initial creation of the instance list for this user
            agentInstanceList = new AgentInstanceList();
            agentInstances.put(agentInstance.user.id, agentInstanceList);
        }

        // Store the instance in the instance list for the user
        agentInstanceList.put(agentInstance);

        // Persist the new agent instance
        persistence.put(agentInstance);

        // Return the agent instance
        return agentInstance;
    }

    public AgentInstance addAgentInstance(User user, JSONObject agentInstanceJson)
            throws AgentServerException, SymbolException, JSONException, TokenizerException, ParserException {
        // Get instance list for the user
        AgentInstance agentInstance = getAgentInstance(user, agentInstanceJson);

        // Return the agent instance
        return agentInstance;
    }

    public AgentInstance getAgentInstance(String userId, String agentInstanceName) {
        return getAgentInstance(getUser(userId), agentInstanceName);
    }

    public AgentInstance getAgentInstance(User user, String agentInstanceName) {
        AgentInstanceList agentMap = agentInstances.get(user.id);
        if (agentMap == null)
            return null;
        else
            return agentMap.get(agentInstanceName);
    }

    public AgentInstance getAgentInstance(User user, AgentDefinition agentDefinition) throws RuntimeException,
            SymbolException, AgentServerException, JSONException, TokenizerException, ParserException {
        return getAgentInstance(user, agentDefinition, null, true);
    }

    public AgentInstance getAgentInstance(User user, AgentDefinition agentDefinition, SymbolValues parameters)
            throws RuntimeException, SymbolException, AgentServerException, JSONException, TokenizerException,
            ParserException {
        return getAgentInstance(user, agentDefinition, parameters, true);
    }

    public AgentInstance getAgentInstance(User user, AgentDefinition agentDefinition, SymbolValues parameters,
            boolean create) throws AgentServerException {
        // Get instance list for the user
        AgentInstanceList agentInstanceList = agentInstances.get(user.id);
        if (agentInstanceList == null) {
            // Need to do the initial creation of the instance list for this user
            agentInstanceList = new AgentInstanceList();
            agentInstances.put(user.id, agentInstanceList);
        }

        // Get the instance from the user's instance list (or add if it doesn't exist yet)
        return agentInstanceList.getAgentInstance(user, agentDefinition, parameters, create);
    }

    public AgentInstance getAgentInstance(String agentJsonString)
            throws SymbolException, RuntimeException, AgentServerException, TokenizerException, ParserException {
        if (agentJsonString == null || agentJsonString.trim().length() == 0)
            agentJsonString = "{}";
        try {
            return getAgentInstance(new JSONObject(agentJsonString));
        } catch (JSONException e) {
            throw new AgentServerException("JSON parsing exception: " + e.getMessage());
        }
    }

    public AgentInstance getAgentInstance(JSONObject agentJson) throws SymbolException, RuntimeException,
            AgentServerException, JSONException, TokenizerException, ParserException {
        return getAgentInstance(null, agentJson);
    }

    public AgentInstance getAgentInstance(User user, JSONObject agentJson) throws SymbolException, RuntimeException,
            AgentServerException, JSONException, TokenizerException, ParserException {
        // Parse the JSON for the agent instance

        // If we have the user, ignore user from JSON
        if (user == null) {
            String userId = agentJson.optString("user");
            if (userId == null || userId.trim().length() == 0)
                throw new AgentServerException("Agent instance user id ('user') is missing");
            user = getUser(userId);
            if (user == User.noUser)
                throw new AgentServerException("Agent instance user id does not exist: '" + userId + "'");
        }

        // Parse the agent instance name
        String agentInstanceName = agentJson.optString("name");
        if (agentInstanceName == null || agentInstanceName.trim().length() == 0)
            throw new AgentServerException("Agent instance name ('name') is missing");

        // Parse the agent definition name
        String agentDefinitionName = agentJson.optString("definition");
        if (agentDefinitionName == null || agentDefinitionName.trim().length() == 0)
            throw new AgentServerException(
                    "Agent instance definition name ('definition') is missing for user '" + user.id + "'");
        String agentDescription = agentJson.optString("description");
        if (agentDescription == null || agentDescription.trim().length() == 0)
            agentDescription = "";
        log.info("Adding new agent instance named: " + agentInstanceName + " for agent definition '"
                + agentDefinitionName + "' for user: " + user.id);
        AgentInstanceList agentMap = agentInstances.get(user.id);
        if (agentMap == null) {
            agentMap = new AgentInstanceList();
            agentInstances.add(user.id, agentMap);
        }

        // Check if referenced agent definition exists
        AgentDefinitionList userAgentDefinitions = agentDefinitions.get(user.id);
        if (userAgentDefinitions == null)
            throw new AgentServerException(
                    "Agent instance '" + agentInstanceName + "' references agent definition '" + agentDefinitionName
                            + "' which does not exist for user '" + user.id + "'");
        AgentDefinition agentDefinition = agentDefinitions.nameValueMap.get(user.id).value.get(agentDefinitionName);
        if (agentDefinition == null)
            throw new AgentServerException(
                    "Agent instance '" + agentInstanceName + "' references agent definition '" + agentDefinitionName
                            + "' which does not exist for user '" + user.id + "'");

        // Parse the agent instance parameter values
        String invalidParameterNames = "";
        SymbolManager symbolManager = new SymbolManager();
        SymbolTable symbolTable = symbolManager.getSymbolTable("parameter_values");
        JSONObject parameterValuesJson = null;
        SymbolValues parameterValues = null;
        if (agentJson.has("parameter_values")) {
            // Parse the parameter values
            parameterValuesJson = agentJson.optJSONObject("parameter_values");
            parameterValues = SymbolValues.fromJson(symbolTable, parameterValuesJson);

            // Validate that they are all valid agent definition parameters
            Map<String, Value> treeMap = new TreeMap<String, Value>();
            for (Symbol symbol : parameterValues)
                treeMap.put(symbol.name, null);
            for (String parameterName : treeMap.keySet())
                if (!agentDefinition.parameters.containsKey(parameterName))
                    invalidParameterNames += (invalidParameterNames.length() > 0 ? ", " : "") + parameterName;
            if (invalidParameterNames.length() > 0)
                throw new AgentServerException("Parameter names for agent instance " + agentInstanceName
                        + " are not defined for referenced agent definition " + agentDefinitionName + ": "
                        + invalidParameterNames);
        }

        // Check if instance of named agent definition with specified parameters exists yet
        if (agentMap.getAgentInstance(user, agentDefinition, parameterValues) != null)
            throw new AgentServerException("Agent instance name already exists: '" + agentInstanceName
                    + "' with paramters " + parameterValues.toString());

        String triggerIntervalExpression = agentJson.optString("trigger_interval",
                AgentDefinition.DEFAULT_TRIGGER_INTERVAL_EXPRESSION);
        String reportingIntervalExpression = agentJson.optString("reporting_interval",
                AgentDefinition.DEFAULT_REPORTING_INTERVAL_EXPRESSION);

        boolean publicOutput = agentJson.optBoolean("public_output", false);

        int limitInstanceStatesStored = agentJson.optInt("limit_instance_states_stored", -1);

        boolean enabled = agentJson.optBoolean("enabled", true);

        String created = agentJson.optString("created", null);
        long timeCreated = -1;
        try {
            timeCreated = created != null ? DateUtils.parseRfcString(created) : -1;
        } catch (ParseException e) {
            throw new AgentServerException("Unable to parse created date ('" + created + "') - " + e.getMessage());
        }
        String modified = agentJson.optString("modified", null);
        long timeModified = -1;
        try {
            timeModified = modified != null ? DateUtils.parseRfcString(modified) : -1;
        } catch (ParseException e) {
            throw new AgentServerException(
                    "Unable to parse modified date ('" + modified + "') - " + e.getMessage());
        }

        AgentInstance agentInstance = agentMap.put(user, agentDefinition, agentInstanceName, agentDescription,
                parameterValues, triggerIntervalExpression, reportingIntervalExpression, publicOutput,
                limitInstanceStatesStored, enabled, timeCreated, timeModified);

        // Persist the new agent instance
        persistence.put(agentInstance);

        // Return the created/shared agent instance
        return agentInstance;
    }

    public AgentInstance getAgentInstance(String userId, String agentInstanceName, String dataSourceName) {
        AgentInstanceList agentMap = agentInstances.get(userId);
        if (agentMap == null)
            return null;

        AgentInstance agentInstance = agentMap.get(agentInstanceName);
        if (agentInstance == null)
            return null;
        return agentInstance.getDataSourceInstance(dataSourceName);
    }

    public String getAgentInstanceName(String userId, String agentInstanceName, String dataSourceName) {
        AgentInstance dataSourceInstance = getAgentInstance(userId, agentInstanceName, dataSourceName);
        if (dataSourceInstance != null)
            return dataSourceInstance.name;
        else
            return null;
    }

    public AgentInstance getAgentInstance(User user, String agentInstanceName, String dataSourceName) {
        AgentInstanceList agentMap = agentInstances.get(user.id);
        if (agentMap == null)
            return null;

        AgentInstance agentInstance = agentMap.get(agentInstanceName);
        if (agentInstance == null)
            return null;
        return agentInstance.getDataSourceInstance(dataSourceName);
    }

    public void removeAgentInstance(AgentInstance agentInstance) throws AgentServerException {
        String userId = agentInstance.user.id;
        String agentName = agentInstance.name;

        // Check if the user has any agents yet
        if (!agentInstances.containsKey(userId))
            throw new AgentServerException("Attempt to delete agent instance ('" + agentName + "') for a user ('"
                    + userId + "') that has no agents");

        // Get agent table for the user
        AgentInstanceList usersAgents = agentInstances.get(userId);

        // Check if that agent exists for user
        if (!usersAgents.containsKey(agentName))
            throw new AgentServerException("Attempt to delete agent instance ('" + agentName
                    + "') that does not exist for user ('" + userId + "')");

        // First step in deleting an agent instance is to pause it
        agentInstance.disable();

        // Wait a little for agent activity to settle down
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // Ignore any non-problem here
        }

        // Flush any pending activities for this agent
        agentScheduler.flushAgentActivities(agentInstance);

        // De-reference any input agents
        agentInstance.deReferenceInputs();

        // Delete the named agent definition for the user
        usersAgents.remove(agentName);
    }

    public User addUser(String userId) throws AgentServerException {
        return addUser(new User(userId));
    }

    public User addUser(User user) throws AgentServerException {
        // Add the new user
        users.add(user.id, user);

        // Persist the new user
        persistence.put(user);

        // Return the new user
        return user;
    }

    public User getUser(String userId) {
        if (userId == null || userId.trim().length() == 0)
            return null;
        else if (userId.equals("*"))
            return User.allUser;
        else {
            User user = users.get(userId);
            return user == null ? User.noUser : user;
        }
    }

    public void recreateUser(String userJsonSource) throws AgentServerException, JSONException {
        User newUser = User.fromJson(new JSONObject(userJsonSource));
        addUser(newUser);
    }

    public void recreateAgentDefinition(String agentDefinitionJsonSource)
            throws AgentServerException, JSONException, SymbolException {
        AgentDefinition newAgentDefinition = AgentDefinition.fromJson(this, agentDefinitionJsonSource);
        addAgentDefinition(newAgentDefinition);
    }

    public void recreateAgentInstance(String agentInstanceJsonSource) throws AgentServerException, JSONException,
            SymbolException, ParseException, TokenizerException, ParserException {
        AgentInstance newAgentInstance = AgentInstance.fromJson(this, agentInstanceJsonSource);
        addAgentInstance(newAgentInstance);
    }

    public void shutdown() throws Exception {
        stop();
        // TODO: Should this do something else in addition to stop?
    }

    public void start() throws AgentServerException, InterruptedException, IOException, PersistentFileException,
            ParseException, TokenizerException, ParserException {
        start(true);
    }

    public void start(boolean start) throws AgentServerException, InterruptedException, IOException,
            PersistentFileException, ParseException, TokenizerException, ParserException {
        // No-op if already started
        if (startTime > 0)
            return;

        // Record start time for server
        startTime = System.currentTimeMillis();

        // Initialize members
        this.users = new NameValueList<User>();
        this.agentDefinitions = new NameValueList<AgentDefinitionList>();
        this.agentInstances = new NameValueList<AgentInstanceList>();

        // Initialize agent server properties
        agentServerProperties = new AgentServerProperties(agentAppServer.commandLineproperties);

        // Initialize the agent scheduler, but keep it suspended for now
        if (agentScheduler == null)
            agentScheduler = new AgentScheduler(this, false);
        else
            agentScheduler.initialize();

        // Initialize persistence
        if (persistence == null)
            persistence = new Persistence(this, getPersistentStorePath());
        else
            persistence.initialize();

        // Force a reload of config
        if (config == null)
            config = new AgentServerConfig(this);
        config.load();

        // Initialize the web access configuration parameters
        if (webAccessConfig == null)
            webAccessConfig = new AgentServerWebAccessConfig(config);

        // Initialize the web site access control lists
        if (webSiteAccessConfig == null)
            webSiteAccessConfig = new WebSiteAccessConfig(this);
        webSiteAccessConfig.load();

        // Initialize the web access manager
        this.webAccessManager = new WebAccessManager(webAccessConfig, webSiteAccessConfig);

        // Initialize the web access manager
        this.mailAccessManager = new MailAccessManager(this);
        mailAccessManager.readConfig();

        // Optionally start scheduler
        if (start)
            agentScheduler.start();
    }

    public void stop() throws AgentServerException, InterruptedException, IOException {
        // First shut down the agent scheduler and wait for it to fully stop
        agentScheduler.shutdown();

        // Now close the persistent store
        persistence.close();

        // Indicate that the agent server is no longer running
        startTime = 0;
    }

    public String getStatus() {
        return agentScheduler.getStatus();
    }

    public String getDefaultReportingInterval() {
        return config.get("default_reporting_interval");
    }

    public String getDefaultTriggerInterval() {
        return config.get("default_trigger_interval");
    }

    public void addWebSiteAccessControls(User user, JSONObject accessControlsJson)
            throws JSONException, AgentServerException {
        webAccessManager.addWebSiteAccessControls(user.id, accessControlsJson);

    }

    public WebPage getWebPage(String userId, String url) {
        return webAccessManager.getWebPage(userId, url);
    }

    public WebPage getWebPage(String userId, String url, boolean useCache, long refreshInterval, boolean wait) {
        return webAccessManager.getWebPage(userId, url, useCache, refreshInterval, wait);
    }

    public WebPage postUrl(String userId, String url, String data) {
        return webAccessManager.postUrl(userId, url, data);
    }

    public WebPage postUrl(String userId, String url, String data, long refreshInterval, boolean wait) {
        return webAccessManager.postUrl(userId, url, data, refreshInterval, wait);
    }

    public ListMap<String, String> getWebSiteAccessControls(User user) throws JSONException, AgentServerException {
        return webAccessManager.getWebSiteAccessControls(user);
    }

    public String getAdminPassword() {
        return config.agentServerProperties.adminPassword;
    }

    public String getPersistentStorePath() {
        return agentServerProperties.persistent_store_dir + "/"
                + AgentServerProperties.DEFAULT_PERSISTENT_STORE_FILE_NAME;
    }

    public long getMinimumTriggerInterval() {
        return config.getLong("minimum_trigger_interval");
    }

    public long getMinimumReportingInterval() {
        return config.getLong("minimum_reporting_interval");
    }
}