/***************************************************************** 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.behaviours; //#MIDP_EXCLUDE_FILE import jade.core.Agent; import jade.lang.acl.ACLMessage; import jade.lang.acl.MessageTemplate; import jade.content.ContentManager; import jade.content.lang.Codec; import jade.content.lang.leap.LEAPCodec; import jade.content.onto.Ontology; import jade.content.onto.OntologyException; import jade.content.onto.basic.Action; import jade.content.onto.basic.Result; import jade.domain.mobility.*; import jade.domain.FIPAAgentManagement.ExceptionVocabulary; import jade.util.leap.List; import jade.util.leap.Iterator; import java.util.Hashtable; import java.util.zip.*; import java.io.*; /** This behaviour serves behaviour-loading requests according to the Behaviour-loading ontology. When an agent runs an instance of this behaviour it becomes able to load and execute completely new behaviours, i.e. behaviours whose code is not included in the classpath of the JVM where the agent lives. Loading behaviour requests must have the <code>ACLMessage.REQUEST</code> performative and must use the BehaviourLoading ontology and the LEAP language. <br> <b>NOT available in MIDP</b> <br> @see jade.domain.mobility.LoadBehaviour @see jade.domain.mobility.BehaviourLoadingOntology @see jade.content.lang.leap.LEAPCodec @author Giovanni Caire - TILAB */ public class LoaderBehaviour extends Behaviour { private Codec codec = new LEAPCodec(); private Ontology onto = BehaviourLoadingOntology.getInstance(); private ContentManager myContentManager = new ContentManager(); private ClassLoader localLoader; private MessageTemplate myTemplate = MessageTemplate.and( MessageTemplate.MatchPerformative(ACLMessage.REQUEST), MessageTemplate.and( MessageTemplate.MatchLanguage(codec.getName()), MessageTemplate.MatchOntology(onto.getName()) ) ); private boolean finished = false; /** Construct a LoaderBehaviour. */ public LoaderBehaviour() { super(); init(); } /** Construct a LoaderBehaviour to be executed by a given agent. */ public LoaderBehaviour(Agent a) { super(a); init(); } /** Construct a LoaderBehaviour to be executed by a given agent and that will use a given class loader to load behaviours whose code is not embedded in the LoadBehaviour request. */ public LoaderBehaviour(Agent a, ClassLoader cl) { super(a); init(); localLoader = cl; } /** The action() method is redefined to serve behaviour loading requests */ public final void action() { if (!finished) { ACLMessage msg = myAgent.receive(myTemplate); if (msg != null) { ACLMessage reply = msg.createReply(); if (accept(msg)) { try { Action actionExpr = (Action) myContentManager.extractContent(msg); LoadBehaviour lb = (LoadBehaviour) actionExpr.getAction(); // Load the behaviour String className = lb.getClassName(); Behaviour b = null; byte[] code = lb.getCode(); byte[] zip = lb.getZip(); if (code != null) { // Try from the class byte code b = loadFromCode(className, code); } else if (zip != null) { // Try from the zip b = loadFromZip(className, zip); } else { // Try using the local loader if (localLoader != null) { b = (Behaviour) localLoader.loadClass(className).newInstance(); } else { b = (Behaviour) Class.forName(className).newInstance(); } } // Set parameters List params = lb.getParameters(); setInputParameters(b, params); // Start the behaviour and prepare a positive reply SequentialBehaviour sb = new SequentialBehaviour(myAgent); sb.addSubBehaviour(b); sb.addSubBehaviour(new ResultCollector(b, params, actionExpr, msg)); myAgent.addBehaviour(sb); reply.setPerformative(ACLMessage.AGREE); } catch (Codec.CodecException ce) { ce.printStackTrace(); reply.setPerformative(ACLMessage.NOT_UNDERSTOOD); reply.setContent("(("+ExceptionVocabulary.UNRECOGNISEDVALUE+" content))"); } catch (OntologyException oe) { oe.printStackTrace(); reply.setPerformative(ACLMessage.NOT_UNDERSTOOD); reply.setContent("(("+ExceptionVocabulary.UNRECOGNISEDVALUE+" content))"); } catch (Exception e) { e.printStackTrace(); reply.setPerformative(ACLMessage.FAILURE); reply.setContent("((internal-error \""+e.toString()+"\"))"); } } else { reply.setPerformative(ACLMessage.REFUSE); } myAgent.send(reply); } else { block(); } } } /** The done() method is redefined to make this behaviour terminate when its <code>stop()</code> method is called. */ public boolean done() { return finished; } /** Make this behaviour terminate. */ public void stop() { finished = true; restart(); } /** Add a loaded behaviour to the agent. Subclasses may redefine this method to handle the behaviour addition operation in an application specific way. @param b The <code>Behaviour</code> to be added. @param request The <code>ACLMessage</code> carrying the <code>LoadBehaviour</code> request. */ protected void addBehaviour(Behaviour b, ACLMessage request) { myAgent.addBehaviour(b); } /** Suclasses may redefine this method to prevent the behaviour loading operation under specific conditions. This default implementation always returns <code>true</code> */ protected boolean accept(ACLMessage msg) { return true; } /////////////////////////// // Private utility methods /////////////////////////// private void init() { // Register LEAP language and BehaviourLoading ontology in a // local ContentManager myContentManager.registerLanguage(codec); myContentManager.registerOntology(onto); localLoader = getClass().getClassLoader(); } private Behaviour loadFromCode(String className, final byte[] code) throws ClassNotFoundException, InstantiationException, IllegalAccessException { Hashtable classes = new Hashtable(1); classes.put(className, code); return load(className, classes); } private Behaviour loadFromZip(String className, byte[] zip) throws ClassNotFoundException, InstantiationException, IllegalAccessException { Hashtable classes = new Hashtable(); try { ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zip)); ZipEntry ze = zis.getNextEntry(); while (ze != null) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] tmp = new byte[1024]; int k = zis.read(tmp, 0, tmp.length); while (k > 0) { baos.write(tmp, 0, k); k = zis.read(tmp, 0, tmp.length); } classes.put(ze.getName(), baos.toByteArray()); zis.closeEntry(); ze = zis.getNextEntry(); } return load(className, classes); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException("Error reading zip for class "+className+". "+e); } } private Behaviour load(String className, Hashtable classes) throws ClassNotFoundException, InstantiationException, IllegalAccessException { ClassLoader loader = new HashClassLoader(classes, getClass().getClassLoader()); Class c = loader.loadClass(className); return (Behaviour) c.newInstance(); } /** Inner class HashClassLoader */ private class HashClassLoader extends ClassLoader { private Hashtable classes; public HashClassLoader(Hashtable ht, ClassLoader parent) { //#PJAVA_EXCLUDE_BEGIN super(parent); //#PJAVA_EXCLUDE_END /*#PJAVA_INCLUDE_BEGIN super(); #PJAVA_INCLUDE_END*/ classes = ht; } protected Class findClass(String name) throws ClassNotFoundException { String fileName = name.replace('.', '/') + ".class"; byte[] code = (byte[]) classes.get(fileName); if (code != null) { return defineClass(name, code, 0, code.length); } else { throw new ClassNotFoundException("Class "+name+" does not exist"); } } /*#PJAVA_INCLUDE_BEGIN In PersonalJava loadClass(String, boolean) is abstract --> we must implement it protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { // 1) Try to see if the class has already been loaded Class c = findLoadedClass(name); // 2) Try to load the class using the system class loader if(c == null) { try { c = findSystemClass(name); } catch (ClassNotFoundException cnfe) { } } // 3) If still not found, try to load the class from the code if(c == null) { c = findClass(name); } if(resolve) { resolveClass(c); } return c; } #PJAVA_INCLUDE_END*/ } // END of inner class HashClassLoader private void setInputParameters(Behaviour b, List params) { DataStore ds = b.getDataStore(); if (params != null) { Iterator it = params.iterator(); while (it.hasNext()) { Parameter p = (Parameter) it.next(); if (p.getMode() == Parameter.IN_MODE || p.getMode() == Parameter.INOUT_MODE) { ds.put(p.getName(), p.getValue()); } } } } private void getOutputParameters(Behaviour b, List params) { DataStore ds = b.getDataStore(); if (params != null) { Iterator it = params.iterator(); while (it.hasNext()) { Parameter p = (Parameter) it.next(); if (p.getMode() == Parameter.OUT_MODE || p.getMode() == Parameter.INOUT_MODE) { p.setValue(ds.get(p.getName())); } } } } /** Inner class ResultCollector. This behaviour restores the output parameters at the end of the execution of the loaded behaviour and sends back the result notification to the requester. */ private class ResultCollector extends OneShotBehaviour { private Behaviour myBehaviour; private List myParams; private Action actionExpr; private ACLMessage request; ResultCollector(Behaviour b, List l, Action a, ACLMessage m) { super(); myBehaviour = b; myParams = l; actionExpr = a; request = m; } public void action() { // Avoid sending back the behaviour code LoadBehaviour lb = (LoadBehaviour) actionExpr.getAction(); lb.setCode(null); lb.setZip(null); // Restore output parameters getOutputParameters(myBehaviour, myParams); Result r = new Result(actionExpr, myParams); ACLMessage notification = request.createReply(); try { myContentManager.fillContent(notification, r); notification.setPerformative(ACLMessage.INFORM); } catch (Exception e) { e.printStackTrace(); notification.setPerformative(ACLMessage.FAILURE); notification.setContent("((internal-error \""+e.toString()+"\"))"); } myAgent.send(notification); } } // END of inner class ResultCollector }