/***************************************************************** JADE - Java Agent DEvelopment Framework is a framework to develop multi-agent systems in compliance with the FIPA specifications. Copyright (C) 2000 CSELT S.p.A. GNU Lesser General Public License This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, version 2.1 of the License. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *****************************************************************/ package jade.core; import jade.imtp.leap.JICP.JICPProtocol; import jade.lang.acl.ACLMessage; import jade.util.leap.Iterator; import jade.util.leap.Properties; import jade.util.Logger; import jade.security.JADESecurityException; //#MIDP_EXCLUDE_BEGIN import jade.core.behaviours.Behaviour; import jade.security.*; //#MIDP_EXCLUDE_END //#J2ME_EXCLUDE_BEGIN import jade.util.ObjectManager; //#J2ME_EXCLUDE_END import java.util.Hashtable; import java.util.Vector; import java.util.Enumeration; /** @author Giovanni Caire - TILAB @author Jerome Picault - Motorola Labs */ class FrontEndContainer implements FrontEnd, AgentToolkit, Runnable { Logger logger = Logger.getMyLogger(this.getClass().getName()); // The table of local agents private Hashtable localAgents = new Hashtable(1); // The table of locally installed services private Hashtable localServices = new Hashtable(1); // The ID of this container private ContainerID myId; // The addresses of the platform this container belongs to Vector platformAddresses; // The AID of the AMS private AID amsAID; // The AID of the default DF private AID dfAID; // The buffer of messages to be sent to the BackEnd private Vector pending; // The list of agents that have pending messages waiting to be delivered // This is used to delay the termination of an agent in case it has pending messages private Vector senderAgents; // The BackEnd this FrontEndContainer is connected to private BackEndWrapper myBackEnd; // The configuration properties for this FrontEndContainer private Properties configProperties; // Flag indicating that the shutdown procedure is in place private boolean exiting = false; // Flag indicating that the startup procedure is in place private boolean starting = true; /** Construct a FrontEndContainer and connect to the BackEnd. */ FrontEndContainer(Properties p) { configProperties = p; // Create all agents without starting them String agents = configProperties.getProperty(MicroRuntime.AGENTS_KEY); try { Vector specs = Specifier.parseSpecifierList(agents); Vector successfulAgents = new Vector(); for (Enumeration en=specs.elements(); en.hasMoreElements(); ) { Specifier s = (Specifier) en.nextElement(); try { localAgents.put(s.getName(), initAgentInstance(s.getName(), s.getClassName(), s.getArgs())); successfulAgents.addElement(s); } catch (Throwable t) { logger.log(Logger.SEVERE, "Exception creating agent "+t); } } configProperties.setProperty(MicroRuntime.AGENTS_KEY, Specifier.encodeSpecifierList(successfulAgents)); } catch(Exception e1){ configProperties.setProperty(MicroRuntime.AGENTS_KEY, null); logger.log(Logger.SEVERE, "Exception parsing agent specifiers "+e1); e1.printStackTrace(); } // Install services if any String feServices = configProperties.getProperty(MicroRuntime.SERVICES_KEY); String beServices = null; Vector svcClasses = Specifier.parseList(feServices, ';'); for (Enumeration en=svcClasses.elements(); en.hasMoreElements(); ) { String serviceClassName = (String) en.nextElement(); try { FEService svc = (FEService) Class.forName(serviceClassName).newInstance(); localServices.put(svc.getName(), svc); beServices = (beServices != null ? beServices+';'+svc.getBEServiceClassName() : svc.getBEServiceClassName()); } catch (Throwable t) { logger.log(Logger.SEVERE, "Exception creating service "+t); } } // Store the list of services to be loaded on the back-end if (beServices != null) { configProperties.setProperty(MicroRuntime.BE_REQUIRED_SERVICES_KEY, beServices); } manageProtoOption(configProperties); // Connect to the BackEnd try { myBackEnd = new BackEndWrapper(this, configProperties); logger.log(Logger.INFO, "--------------------------------------\nAgent container " + myId.getName() + " is ready.\n--------------------------------------------"); } catch (IMTPException imtpe) { logger.log(Logger.SEVERE,"IMTP error "+imtpe); imtpe.printStackTrace(); MicroRuntime.handleTermination(true); return; } catch (Exception e) { logger.log(Logger.SEVERE,"Unexpected error "+e); e.printStackTrace(); MicroRuntime.handleTermination(true); return; } // Connect installed services with the BackEnd for (Enumeration en=localServices.elements(); en.hasMoreElements(); ) { FEService svc = (FEService) en.nextElement(); svc.init(myBackEnd); } // Start all agents that have been successfully accepted by the main. // NOTE that after the BackEnd creation, each agent-specifier takes the form // <original-name>:<str1>[(str2)] // where if str2 is NOT present --> the agent was accepted by the main // and str1 represents the actual agent name (with wild cards, if any, properly // replaced), else there was an exception and str1 is the exception class name // and str2 is the exception message. agents = configProperties.getProperty(MicroRuntime.AGENTS_KEY); try{ Vector specs = Specifier.parseSpecifierList(agents); // Start all agents Enumeration e = specs.elements(); while (e.hasMoreElements()) { Specifier sp = (Specifier) e.nextElement(); Agent a = (Agent) localAgents.remove(sp.getName()); if(a != null){ Object[] args = sp.getArgs(); if((args != null) && args.length >0){ //there was an exception notifying the main... logger.log(Logger.SEVERE, "Error starting agent " + sp.getName() + ". " + sp.getClassName() + " " + args[0]); }else{ String actualName = sp.getClassName(); localAgents.put(actualName, a); AID id = new AID(actualName, AID.ISLOCALNAME); a.powerUp(id, new Thread(a)); } }else{ logger.log(Logger.WARNING, "Agent " + sp.getName() + " not found locally."); } } } catch (Exception e1) { logger.log(Logger.SEVERE,"Exception parsing agent specifiers "+e1); e1.printStackTrace(); } //clear the agent specifiers to avoid sending them again to the back end //during BE re-creations. configProperties.remove(MicroRuntime.AGENTS_KEY); notifyStarted(); } private void manageProtoOption(Properties pp) { String proto = pp.getProperty(MicroRuntime.PROTO_KEY); if (proto != null) { // This option assumes the single-connection approach in case of SOCKET and SSL and usage of NIO back-end side if (CaseInsensitiveString.equalsIgnoreCase(MicroRuntime.SOCKET_PROTOCOL, proto)) { pp.setProperty(MicroRuntime.CONN_MGR_CLASS_KEY, "jade.imtp.leap.JICP.FrontEndDispatcher"); } else if (CaseInsensitiveString.equalsIgnoreCase(MicroRuntime.SSL_PROTOCOL, proto)) { pp.setProperty(MicroRuntime.CONN_MGR_CLASS_KEY, "jade.imtp.leap.JICP.FrontEndSDispatcher"); } else if (CaseInsensitiveString.equalsIgnoreCase(MicroRuntime.HTTP_PROTOCOL, proto)) { pp.setProperty(MicroRuntime.CONN_MGR_CLASS_KEY, "jade.imtp.leap.http.HTTPFEDispatcher"); pp.setProperty(JICPProtocol.MEDIATOR_CLASS_KEY, "jade.imtp.leap.nio.NIOHTTPBEDispatcher"); } else if (CaseInsensitiveString.equalsIgnoreCase(MicroRuntime.HTTPS_PROTOCOL, proto)) { pp.setProperty(MicroRuntime.CONN_MGR_CLASS_KEY, "jade.imtp.leap.http.HTTPFESDispatcher"); pp.setProperty(JICPProtocol.MEDIATOR_CLASS_KEY, "jade.imtp.leap.nio.NIOHTTPBEDispatcher"); } } } void detach() { myBackEnd.detach(); } //#MIDP_EXCLUDE_BEGIN /** * Request the FrontEnd to return a local agent reference by his local name */ final Agent getLocalAgent(String localName) { return (Agent) localAgents.get(localName); } //#MIDP_EXCLUDE_END ///////////////////////////////////// // FrontEnd interface implementation ///////////////////////////////////// /** Request the FrontEnd container to create a new agent. @param name The name of the new agent. @param className The class of the new agent. @param args The arguments to be passed to the new agent. */ public final void createAgent(String name, String className, String[] args) throws IMTPException { try { Agent a = initAgentInstance(name, className, (Object[]) args); String newName = myBackEnd.bornAgent(name); localAgents.put(newName, a); AID id = new AID(newName, AID.ISLOCALNAME); a.powerUp(id, new Thread(a)); } catch (Exception e) { String msg = "Exception creating new agent. "; logger.log(Logger.SEVERE,msg+e); throw new IMTPException(msg, e); } } /** Request the FrontEnd container to kill an agent. @param name The name of the agent to kill. */ public final void killAgent(String name) throws NotFoundException, IMTPException { waitUntilStarted(); Agent agent = (Agent) localAgents.get(name); if(agent == null) { System.out.println("FrontEndContainer killing: " + name + " NOT FOUND"); throw new NotFoundException("KillAgent failed to find " + name); } // Note that the agent will be removed from the local table in // the handleEnd() method. agent.doDelete(); } /** Request the FrontEnd container to suspend an agent. @param name The name of the agent to suspend. */ public final void suspendAgent(String name) throws NotFoundException, IMTPException { waitUntilStarted(); Agent agent = (Agent) localAgents.get(name); if(agent == null) { throw new NotFoundException("SuspendAgent failed to find " + name); } agent.doSuspend(); } /** Request the FrontEnd container to resume an agent. @param name The name of the agent to resume. */ public final void resumeAgent(String name) throws NotFoundException, IMTPException { waitUntilStarted(); Agent agent = (Agent) localAgents.get(name); if(agent == null) { throw new NotFoundException("ResumeAgent failed to find " + name); } agent.doActivate(); } /** Pass an ACLMessage to the FrontEnd for posting. @param msg The message to be posted. @param sender The name of the receiver agent. */ public final void messageIn(ACLMessage msg, String receiver) throws NotFoundException, IMTPException { waitUntilStarted(); if (receiver != null) { Agent agent = (Agent) localAgents.get(receiver); if(agent == null) { throw new NotFoundException("Receiver "+receiver+" not found"); } agent.postMessage(msg); } } /** Request the FrontEnd container to exit. */ public final void exit(boolean self) throws IMTPException { if (!exiting) { exiting = true; logger.log(Logger.INFO,"Container shut down activated"); // Kill all agents. We first get a snapshot of all agents and then scan it to kill them. // This is to avoid deadlock with handleEnd() that calls localAgents.remove() Vector toBeKilled = new Vector(); synchronized (localAgents) { Enumeration e = localAgents.elements(); while (e.hasMoreElements()) { toBeKilled.addElement(e.nextElement()); } } Enumeration e = toBeKilled.elements(); while (e.hasMoreElements()) { // Kill agent and wait for its termination Agent a = (Agent) e.nextElement(); a.doDelete(); a.join(); a.resetToolkit(); } localAgents.clear(); logger.log(Logger.FINE,"Local agents terminated"); // Shut down the connection with the BackEnd. The BackEnd will // exit and deregister with the main myBackEnd.detach(); logger.log(Logger.FINE,"Connection manager closed"); // Notify the JADE Runtime that the container has terminated execution MicroRuntime.handleTermination(self); // Stop the TimerDispatcher if it was activated TimerDispatcher.getTimerDispatcher().stop(); } } /** Request the FrontEnd container to synch. */ public final void synch() throws IMTPException { synchronized (localAgents) { Enumeration e = localAgents.keys(); while (e.hasMoreElements()) { String name = (String) e.nextElement(); logger.log(Logger.INFO,"Resynching agent "+name); try { // Notify the BackEnd (note that the agent name will never change in this case) myBackEnd.bornAgent(name); } catch (IMTPException imtpe) { // The connection is likely down again. Rethrow the exception // to make the BE repeat the synchronization process logger.log(Logger.WARNING,"IMTPException resynching. "+imtpe); throw imtpe; } catch (Exception ex) { logger.log(Logger.SEVERE,"Exception resynching agent "+name+". "+ex); ex.printStackTrace(); // An agent with the same name has come up in the meanwhile. // FIXME: Kill the agent or notify a warning } } } } /** * It may happen that a message for a bootstrap agent using wildcards in its name is received before * the actual agent name is assigned --> This method is called in messageIn() and other agent-related * methods to avoid that. */ private synchronized void waitUntilStarted() { try { while (starting) { wait(); } } catch (Exception e) {} } private synchronized void notifyStarted() { starting = false; notifyAll(); } ///////////////////////////////////// // AgentToolkit interface implementation ///////////////////////////////////// //#MIDP_EXCLUDE_BEGIN public jade.wrapper.AgentContainer getContainerController(JADEPrincipal principal, Credentials credentials){ return null; } //#MIDP_EXCLUDE_END public final Location here() { return myId; } public final void handleEnd(AID agentID) { String name = agentID.getLocalName(); // Wait for messages (if any) sent by this agent to be transmitted if (pending != null) { synchronized (pending) { while (senderAgents.contains(name)) { try { pending.wait(); } catch (Exception e) {} } } } if (!exiting) { // If this agent is ending because the container is exiting // just do nothing. The BackEnd will notify the main. try { localAgents.remove(name); myBackEnd.deadAgent(name); // If there are no more agents and the exitwhenempty option // is set, activate shutdown if ("true".equals(configProperties.getProperty("exitwhenempty"))) { if (localAgents.isEmpty()) { exit(true); } } } catch(IMTPException re) { logger.log(Logger.SEVERE,re.toString()); } } } public final void handleChangedAgentState(AID agentID, int from, int to) { // FIXME: This should call myBackEnd.suspendedAgent()/resumedAgent() } // Note that the needClone argument is ignored since the // FrontEnd must always clone public final void handleSend(ACLMessage msg, AID sender, boolean needClone) { Iterator it = msg.getAllIntendedReceiver(); // If some receiver is local --> directly post the message boolean hasRemoteReceivers = false; while (it.hasNext()) { AID id = (AID) it.next(); Agent a = (Agent) localAgents.get(id.getLocalName()); if (a != null) { ACLMessage m = (ACLMessage) msg.clone(); a.postMessage(m); } else { hasRemoteReceivers = true; } } // If some receiver is remote --> pass the message to the BackEnd if (hasRemoteReceivers) { post(msg, sender.getLocalName()); } } public final void setPlatformAddresses(AID id) { id.clearAllAddresses(); for (int i = 0; i < platformAddresses.size(); ++i) { id.addAddresses((String)platformAddresses.elementAt(i)); } } public final AID getAMS() { return amsAID; } public final AID getDefaultDF() { return dfAID; } public String getProperty(String key, String aDefault) { String ret = configProperties.getProperty(key); return (ret != null ? ret : aDefault); } //#MIDP_EXCLUDE_BEGIN public Properties getBootProperties(){ /* TODO To be completed */ return null; } public void handleMove(AID agentID, Location where) throws JADESecurityException, IMTPException, NotFoundException { } public void handleClone(AID agentID, Location where, String newName) throws JADESecurityException, IMTPException, NotFoundException { } public void handleSave(AID agentID, String repository) throws ServiceException, NotFoundException, IMTPException { } public void handleReload(AID agentID, String repository) throws ServiceException, NotFoundException, IMTPException { } public void handleFreeze(AID agentID, String repository, ContainerID bufferContainer) throws ServiceException, NotFoundException, IMTPException { } public void handlePosted(AID agentID, ACLMessage msg) { } public void handleReceived(AID agentID, ACLMessage msg) { } public void handleBehaviourAdded(AID agentID, Behaviour b) { } public void handleBehaviourRemoved(AID agentID, Behaviour b) { } public void handleChangeBehaviourState(AID agentID, Behaviour b, String from, String to) { } //#MIDP_EXCLUDE_END public ServiceHelper getHelper(Agent a, String serviceName) throws ServiceException { FEService svc = (FEService) localServices.get(serviceName); if (svc != null) { return svc.getHelper(a); } else { throw new ServiceNotActiveException(serviceName); } } /////////////////////////////// // Private methods /////////////////////////////// final void initInfo(Properties pp) { myId = new ContainerID(pp.getProperty(MicroRuntime.CONTAINER_NAME_KEY), null); AID.setPlatformID(pp.getProperty(MicroRuntime.PLATFORM_KEY)); platformAddresses = Specifier.parseList(pp.getProperty(MicroRuntime.PLATFORM_ADDRESSES_KEY), ';'); amsAID = new AID("ams", AID.ISLOCALNAME); setPlatformAddresses(amsAID); dfAID = new AID("df", AID.ISLOCALNAME); setPlatformAddresses(dfAID); } private final Agent initAgentInstance(String name, String className, Object[] args) throws Exception { Agent agent = null; //#J2ME_EXCLUDE_BEGIN agent = (Agent) ObjectManager.load(className, ObjectManager.AGENT_TYPE); //#J2ME_EXCLUDE_END if (agent == null) { agent = (Agent) Class.forName(className).newInstance(); } agent.setArguments(args); agent.setToolkit(this); //#MIDP_EXCLUDE_BEGIN agent.initMessageQueue(); //#MIDP_EXCLUDE_END return agent; } private void post(ACLMessage msg, String sender) { if (pending == null) { // Lazily create the vector of pending messages, the Thread // for asynchronous message delivery and the vector of senderAgents pending = new Vector(4); senderAgents = new Vector(1); Thread t = new Thread(this); t.start(); } synchronized(pending) { if (!senderAgents.contains(sender)) { senderAgents.addElement(sender); } pending.addElement(msg.clone()); pending.addElement(sender); int size = pending.size(); if (size > 100 && size < 110) { logger.log(Logger.INFO,size+" pending messages"); } pending.notifyAll(); } } public void run() { ACLMessage msg = null; String sender = null; while (true) { synchronized(pending) { while (pending.size() == 0) { try { pending.wait(); } catch (InterruptedException ie) { // Should never happen logger.log(Logger.SEVERE,ie.toString()); } } msg = (ACLMessage) pending.elementAt(0); sender = (String) pending.elementAt(1); pending.removeElementAt(1); pending.removeElementAt(0); } try { myBackEnd.messageOut(msg, sender); } catch (Exception e) { // Should never happen. Note that "NotFound" here is referred // to the sender. logger.log(Logger.SEVERE,e.toString()); } // Notify terminating agents (if any) waiting for their messages to be delivered synchronized (pending) { if (!pending.contains(sender)) { // No more pending messages from this agent senderAgents.removeElement(sender); pending.notifyAll(); } } } } }