/*****************************************************************
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.ArrayList;
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 any. Otherwise load from the classpath
if (localLoader != null) {
b = (Behaviour) Class.forName(className, true, localLoader).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 = Class.forName(className, true, loader);
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
protected 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());
}
}
}
}
protected 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);
if (myParams == null) {
// Result cannot be null
myParams = new ArrayList();
}
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
}