fr.dyade.aaa.agent.Agent Maven / Gradle / Ivy
Show all versions of a3-rt Show documentation
/*
* Copyright (C) 2001 - 2024 ScalAgent Distributed Technologies
* Copyright (C) 1996 - 2000 BULL
* Copyright (C) 1996 - 2000 INRIA
*
* 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; either
* version 2.1 of the License, or any later version.
*
* 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 fr.dyade.aaa.agent;
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import fr.dyade.aaa.common.Debug;
import fr.dyade.aaa.common.encoding.Decoder;
import fr.dyade.aaa.common.encoding.Encodable;
import fr.dyade.aaa.common.encoding.EncodableHelper;
import fr.dyade.aaa.common.encoding.Encoder;
import fr.dyade.aaa.util.management.MXWrapper;
/**
* The Agent
class represents the basic component in our model.
* agents are "reactive" objects which behave according to
* "event/reaction"model: an event embodies a significant state change
* which one or many agents may react to.
* Class Agent
defines the generic interface and the common
* behavior for all agents; every agent is an object of a class deriving
* from class Agent. Agents are the elementary programming and execution
* entities; they only communicate using notifications through the message
* bus, and are controlled by the execution engine.
* The reactive behavior is implemented by function member React, which
* defines the reaction of the agent when receiving a notification; this
* function member is called by the execution engine.
* Agents are persistent objects, and the Agent class realizes a
* "swap-in/swap-out" mechanism which allows loading (or finding) in main
* memory the agents to activate, and unloading the agents idle since a while.
*
* Agents must be created in two steps:
*
* - locally creating the object in memory (via constructor),
*
- configure it (for example via get/set methods),
*
- the deploy it .
*
*
* The following code would then create a simple agent and deploy it:
*
* Agent ag = new Agent();
* ag.deploy();
*
*
* @see Notification
* @see Engine
* @see Channel
*/
public abstract class Agent implements AgentMBean, Serializable, Encodable {
/** Define serialVersionUID for interoperability. */
static final long serialVersionUID = 1L;
/**
* true
if the agent state has changed.
*
* This field value is initialized as true
, so that by default
* the agent state is saved after a reaction.
*/
private transient boolean updated = true;
transient int reactNb = 0;
/**
* @return the reactNb
*/
public int getReactNb() {
return reactNb;
}
/**
* Increments the tick counter that reflects activity in server.
*/
public final void incWorkInProgress() {
AgentServer.getEngine().incWorkInProgress();
}
/**
* Boolean value indicating if the agent profiling is on.
* If true, the cumulative time of reaction and commit is kept for this agent.
*/
public boolean agentProfiling = false;
/**
* Returns true if the agent profiling is on.
*
* @return true if the agent profiling is on.
* @see fr.dyade.aaa.agent.EngineMBean#isAgentProfiling()
*/
public boolean isAgentProfiling() {
return this.agentProfiling;
}
/**
* Sets the agent profiling.
*
* @param agentProfiling if true sets the agent profiling.
* @see fr.dyade.aaa.agent.EngineMBean#setAgentProfiling(boolean)
*/
public void setAgentProfiling(boolean agentProfiling) {
this.agentProfiling = agentProfiling;
}
transient long reactTime = 0L;
/**
* @return the reactTime
*/
public long getReactTime() {
return reactTime;
}
/**
* reset the reactTime
*/
public void resetReactTime(){
reactTime = 0;
}
transient long commitTime = 0L;
/**
* @return the commitTime
*/
public long getCommitTime() {
return commitTime;
}
/**
* reset the commitTime
*/
public void resetCommitTime(){
commitTime = 0;
}
/**
* Reset reactTime and commitTime
*/
public void resetTimer(){
resetReactTime();
resetCommitTime();
}
/**
* Sets the updated
field to false
so that the
* agent state is not saved after the current reaction; the field is set
* back to true
for the next reaction.
*/
protected void setNoSave() {
updated = false;
}
/**
* Sets the updated
field to true
so that the
* agent state is saved after the current reaction.
*/
protected void setSave() {
updated = true;
}
/**
* Indicates whether the agent has been updated or not.
* @return true if the agent has been updated.
*/
public boolean isUpdated() {
return updated;
}
/**
* Indicates to the Engine component that a commit is needed.
* @return true if there is no error.
*/
protected final boolean needToBeCommited() {
try {
((EngineThread) Thread.currentThread()).engine.needToBeCommited = true;
return true;
} catch (ClassCastException exc) {
return false;
}
}
/**
* Saves the agent state unless not requested.
* @throws IOException if any error occurs.
*/
protected final void save() throws IOException {
agentSave();
if (updated) {
AgentServer.getTransaction().save(this, id.toString());
if (logmon.isLoggable(BasicLevel.DEBUG))
logmon.log(BasicLevel.DEBUG,
"Agent" + id + " [" + name + "] saved");
} else {
updated = true;
if (logmon.isLoggable(BasicLevel.DEBUG))
logmon.log(BasicLevel.DEBUG,
"Agent" + id + " [" + name + "] not saved");
}
}
/**
* Enables the sub-classes to save their state.
* @throws IOException if any error occurs.
*/
protected void agentSave() throws IOException {
// Nothing to save
}
/**
* Restores the object state from the persistent storage.
*
* @param id The agent unique identifier.
* @return The agent object.
* @throws IOException when accessing the stored image.
* @throws ClassNotFoundException if the stored image class may not be found.
*/
final static Agent
load(AgentId id) throws IOException, ClassNotFoundException {
Agent ag = (Agent) AgentServer.getTransaction().load(id.toString());
if (ag != null) {
ag.id = id;
ag.deployed = true;
}
return ag;
}
// Declares all fields transient in order to avoid useless
// description of each during serialization.
/**
* Global unique identifier of the agent. Each agent is identified by a
* unique identifier allowing the agent to be found. The identifiers format
* is detailed in AgentId class.
*/
transient AgentId id;
/** Symbolic name of the agent */
transient String name;
public boolean hasName() {
return (name != null);
}
/**
* Returns this Agent
's name.
* If the name is not set returns the string representation of its unique id.
*
* @return this Agent
's name.
*/
public String getName() {
if ((name == null) || (name.length() == 0))
return id.toString();
return name;
}
/**
* Sets this Agent
's name.
*
* @param name the Agent
's name.
*/
public void setName(String name) {
this.name = name;
}
/**
* Some agents must be loaded at any time, this can be enforced by this
* member variable. If true
agent is pinned in memory.
*/
protected transient boolean fixed;
protected transient Logger logmon = null;
/**
* Returns default log topic for agents. Its method should be overridden
* in subclass in order to permit fine configuration of logging system.
* By default it returns Debug.A3Agent
.
*
* @return the default log topic for agents.
*/
protected String getLogTopic() {
return Agent.class.getName();
}
/**
* the last
variable contains the virtual time of the
* last access. It is used by swap-out policy.
*/
transient long last;
public static final String emptyString = "";
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
if (name == null)
out.writeUTF(emptyString);
else
out.writeUTF(name);
out.writeBoolean(fixed);
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
name = in.readUTF();
if (name.length() == 0)
name = null;
fixed = in.readBoolean();
updated = true;
}
/**
* Allocates a new Agent object. The resulting object is not an agent;
* before it can react to a notification you must deploy it. This constructor
* has the same effect as
* Agent(AgentServer.getServerId(), null, false)
.
*
* @see Agent#Agent(short, java.lang.String, boolean)
* @see #deploy()
*/
public Agent() {
this(null, false);
}
/**
* Allocates a new Agent object. This constructor has the same effect
* as Agent(AgentServer.getServerId(), null, fixed)
.
*
* @param fixed if true
agent is pinned in memory
*
* @see Agent#Agent(short, String, boolean)
*/
public Agent(boolean fixed) {
this(null, fixed);
}
/**
* Allocates a new Agent object. This constructor has the same effect
* as Agent(AgentServer.getServerId(), name, false)
.
*
* @param name symbolic name
*
* @see Agent#Agent(short, java.lang.String, boolean)
*/
public Agent(String name) {
this(name, false);
}
/**
* Allocates a new Agent object. This constructor has the same effect
* as Agent(AgentServer.getServerId(), name, fixed)
.
*
* @param name symbolic name
* @param fixed if true
agent is pinned in memory
*
* @see Agent#Agent(short, java.lang.String, boolean)
*/
public Agent(String name, boolean fixed) {
this(AgentServer.getServerId(), name, fixed);
}
/**
* Allocates a new Agent object. This constructor has the same effect
* as Agent(to, null, false)
.
*
* @param to Identification of target agent server
*
* @see Agent#Agent(short, java.lang.String, boolean)
*/
public Agent(short to) {
this(to, null, false);
}
/**
* Allocates a new Agent object. This constructor has the same effect
* as Agent(to, name, false)
.
*
* @param to Identification of target agent server
* @param name symbolic name
*
* @see Agent#Agent(short, java.lang.String, boolean)
*/
public Agent(short to, String name) {
this(to, name, false);
}
/**
* Allocates a new Agent object. This constructor has the same effect
* as Agent(to, null, fixed)
.
*
* @param to Identification of target agent server
* @param fixed if true
agent is pinned in memory
*
* @see Agent#Agent(short, java.lang.String, boolean)
*/
public Agent(short to, boolean fixed) {
this(to, null, fixed);
}
/**
* Allocates a new Agent object. The resulting object is not an agent;
* before it can react to a notification you must deploy it.
*
* @param to Identification of target agent server
* @param name symbolic name
* @param fixed if true
agent is pinned in memory
*
* @see #deploy()
*/
public Agent(short to, String name, boolean fixed) {
AgentId id = null;
try {
id = new AgentId(to);
} catch (IOException exc) {
logmon = Debug.getLogger(Agent.class.getName() + ".#" + AgentServer.getServerId());
logmon.log(BasicLevel.ERROR,
AgentServer.getName() + ", can't allocate new AgentId", exc);
// TODO: throw an exception...
}
initState(name, fixed, id);
}
/**
* Constructor used to build "system" agents like AgentFactory
.
* System agents are created from the agent
package. This
* constructor takes the agent id as a parameter instead of building it.
*
* @param name symbolic name
* @param fixed if true
agent is pinned in memory
* @param id unique identifier
*/
Agent(String name, boolean fixed, AgentId id) {
initState(name, fixed, id);
}
private void initState(String name, boolean fixed, AgentId id) {
this.name = name;
this.fixed = fixed;
this.id = id;
// Get the logging monitor from current server MonologLoggerFactory
this.logmon = Debug.getLogger(getLogTopic());
}
/**
* Constructor used to build Well Known Services agents.
*
* System agents are created from the agent
package.
* WKS agents are similar to system agents, except that they may be
* defined in separate packages, and they do not necessarily exist on all
* agent servers. Their creation is controlled from the configuration file
* of the agent server.
* This constructor takes the agent id as a parameter instead of building it.
* Since the constructor has been made public, the consistency of agent ids
* allocation must be enforced. This is done by the constructor checking
* that the id stamp is comprised in the AgentId.MinWKSIdStamp
* - AgentId.MaxWKSIdStamp
interval.
*
* @param name symbolic name
* @param fixed if true
agent is pinned in memory
* @param stamp well known stamp
*/
public Agent(String name, boolean fixed, int stamp) {
if (stamp < AgentId.MinWKSIdStamp || stamp > AgentId.MaxWKSIdStamp) {
logmon = Debug.getLogger(Agent.class.getName() + ".#" + AgentServer.getServerId());
logmon.log(BasicLevel.ERROR,
AgentServer.getName() + ", well known service stamp out of range: " + stamp);
throw new IllegalArgumentException("Well known service stamp out of range: " + stamp);
}
AgentId id = new AgentId(AgentServer.getServerId(),
AgentServer.getServerId(),
stamp);
initState(name, fixed, id);
}
/**
* Determines if the current Agent
has already been deployed.
*/
transient boolean deployed = false;
/**
* Returns if the currently Agent
has already been deployed.
*
* @return true if the current agent has already been deployed.
*/
public boolean isDeployed() {
return deployed;
}
/**
* Deploys a new agent.
* It works by sending a notification to a special agent, of class Factory,
* running on the target agent server. The notification asks for a remote
* creation of the agent. This solution presents the advantage of reusing
* the standard communication mechanisms of the agent machine.
* The whole process involves then the following steps:
*
* - serializing the object state,
*
- building an
AgentCreateRequest
notification with the
* resulting bytes stream,
* - sending it to the target Factory agent.
*
* In reaction, the factory agent builds the agent in the target server
* from the serialized image, and saves it into operational storage.
*
* @exception IOException
* unspecialized exception
*/
public final void deploy() throws IOException {
deploy(null);
}
/**
* Deploys a new agent.
* It works as deploy() method above; after the
* agent creation, the Factory agent sends an AgentCreateReply
* notification.
*
* @param reply agent to reply to
* @exception IOException
* unspecialized exception
*/
public final void deploy(AgentId reply) throws IOException {
if ((id == null) || id.isNullId()) {
logmon.log(BasicLevel.ERROR,
AgentServer.getName() + ", can't deploy " + this.toString() + ", id is null");
throw new IOException("Can't deploy agent, id is null");
}
if (deployed) {
logmon.log(BasicLevel.ERROR,
AgentServer.getName() + ", can't deploy " + this.toString() + ", already deployed");
throw new IOException("Can't deploy agent, already deployed");
}
// If we use sendTo agent's method the from field is the agent id, and
// on reception the from node (from.to) can be false.
Channel.sendTo(AgentId.factoryId(id.getTo()),
new AgentCreateRequest(this, reply));
deployed = true;
if (logmon.isLoggable(BasicLevel.DEBUG))
logmon.log(BasicLevel.DEBUG, this.toString() + " deployed");
}
/**
* Returns a string representation of this agent, including the agent's
* class, name, global identification, and fixed property.
*
* @return A string representation of this agent.
*/
public String toString() {
StringBuffer strbuf = new StringBuffer();
strbuf.append('(').append(super.toString());
strbuf.append(",name=").append(name);
strbuf.append(",id=").append(id.toString());
strbuf.append(",fixed=").append(fixed);
strbuf.append(')');
return strbuf.toString();
}
/**
* Returns String format of the global unique identifier of the agent.
*
* @return the global unique identifier of the agent.
*/
public final String getAgentId() {
return id.toString();
}
/**
* Returns the global unique identifier of the agent. Each agent is
* identified by a unique identifier allowing the agent to be found.
* The identifiers format is detailed in AgentId
* class.
*
* @return the global unique identifier of the agent.
*/
public final AgentId getId() {
return id;
}
/**
* Tests if the agent is pinned in memory.
*
* @return true if this agent is a pinned in memory; false otherwise.
*/
public final boolean isFixed() {
return fixed;
}
/**
* Gives this agent an opportunity to initialize after having been deployed,
* and each time it is loaded into memory.
*
* This function is first called by the factory agent, just after it deploys
* the agent.
*
* This function is used by agents with a fixed
field set to
* true
to initialize their transient variables, as it is called
* each time the agent server is restarted.
*
* This function is not declared final
so that derived classes
* may change their reload policy. The implementation of this method provided
* by the Agent
class just registers the JMS MBean.
*
* @param firstTime true when first called by the factory
*
* @exception Exception
* unspecialized exception
*/
protected void agentInitialize(boolean firstTime) throws Exception {
// Get the logging monitor from current server MonologLoggerFactory
this.logmon = Debug.getLogger(getLogTopic());
// Initializes the updated field to true:
this.updated = true;
// TODO (AF): /!\ Registering all loaded agents in JMX may cause scalability issues!!
// May be, we can do it only in a special debug mode.
try {
MXWrapper.registerMBean(this, "AgentServer", getA3MBeanName());
} catch (Exception exc) {
if (logmon.isLoggable(BasicLevel.DEBUG)) {
logmon.log(BasicLevel.WARN,
"Agent" + id + " [" + name + "]: JMX registration failed", exc);
} else {
logmon.log(BasicLevel.WARN,
"Agent" + id + " [" + name + "]: JMX registration failed");
}
}
if (logmon.isLoggable(BasicLevel.DEBUG))
logmon.log(BasicLevel.DEBUG,
"Agent" + id + " [" + name + "], initialized: " + firstTime);
}
private final String getA3MBeanName() {
StringBuffer strbuf = new StringBuffer();
strbuf.append("server=").append(AgentServer.getName());
strbuf.append(",cons=Engine#").append(getId().getTo());
if ((name == null) || (name.length() == 0))
strbuf.append(",agent=").append(getAgentId());
else
strbuf.append(",agent=").append(name).append('[').append(getAgentId()).append(']');
return strbuf.toString();
}
/**
* This method sends a notification to the agent which id is given in
* parameter. During an agent reaction alls notifications sent are buffered
* until reaction commit.
*
* Be careful if you use this method outside of an agent reaction,
* its behavior is slightly different: each notification is immediately
* sent using a local transaction.
*
* @see Channel#sendTo
*
* @param to the unique id. of destination Agent
.
* @param not the notification to send.
*/
protected final void
sendTo(AgentId to, Notification not) {
// try {
// EngineThread thread = (EngineThread) Thread.currentThread();
// // Use the engine's sendTo method that push message in temporary queue
// // until the end of current reaction.
// thread.engine.push(getId(), to, not);
// } catch (ClassCastException exc) {
// // Be careful, the destination node use the from.to field to
// // get the from node id.
// Channel.channel.directSendTo(getId(), to, not);
// }
// if (Class.EngineThread.isAssignable(Thread.currentThread())) {
if (AgentServer.isEngineThread()) {
AgentServer.engine.push(getId(), to, not);
} else {
Channel.channel.directSendTo(getId(), to, not);
}
}
/**
* This method sends a notification to the agent which id is wrapped
* in the specified role.
*
* @param role the destination Role
.
* @param not the notification to send.
*/
protected final void sendTo(Role role, Notification not) {
if (role == null) return;
sendTo(role.getListener(), not);
}
/**
* Sends a notification to all the agents registered in a role.
*
* @param role the destination MultiplRole
.
* @param not the notification to send.
*/
protected final void sendTo(RoleMultiple role, Notification not) {
if (role == null) return;
Enumeration to = role.getListeners();
if (to == null) return;
while (to.hasMoreElements())
sendTo((AgentId) to.nextElement(), not);
}
/**
* Permits this agent to destroy itself. If necessary, this method should be
* overloaded to work properly.
*/
public void delete() {
delete(null);
}
/**
* Permits this agent to destroy itself. If necessary, this method should be
* overloaded to work properly.
*
* @param agent Id of agent to notify.
*/
public void delete(AgentId agent) {
if (deployed)
sendTo(AgentId.factoryId(id.getTo()),
new AgentDeleteRequest(agent));
}
/**
* Permits this agent to destroy itself. If necessary, this method should be
* overloaded to work properly.
*
* @param agent
* Id of agent to notify.
* @param extraInformation
* extra information added when notifying the agent.
*/
public void delete(AgentId agent, Object extraInformation) {
if (deployed)
sendTo(AgentId.factoryId(id.getTo()), new AgentDeleteRequest(agent, extraInformation));
}
/**
* Defines the reaction of the agent when receiving a notification. This
* member function implements the common reactive behavior of an agent, it
* is called by the execution engine (see Engine
* class).
* If there is no corresponding reaction, the agent send an
* UnknownNotification
notification to the sender.
*
* @param from agent sending notification
* @param not notification to react to
*
* @exception Exception
* unspecialized exception
*/
public void react(AgentId from, Notification not) throws Exception {
if (not instanceof DeleteNot) {
delete(((DeleteNot) not).reply);
} else if (not instanceof AdminSyncNotification) {
doAdminSyncNotification(from, (AdminSyncNotification) not);
} else if ((not instanceof UnknownAgent) ||
(not instanceof UnknownNotification) ||
(not instanceof ExceptionNotification)) {
logmon.log(BasicLevel.WARN,
this.toString() + ".react(" + from + ", " + not + ")");
} else {
logmon.log(BasicLevel.ERROR,
"Unknown notification, " + this.toString() + ".react(" + from + ", " + not + ")");
sendTo(from, new UnknownNotification(id, not));
}
}
/**
* Default handling of AdminSyncNotification.
* Currently there is no command defined at the Agent level, this method just logs a warning, and
* unlocks the caller.
*
* @param from
* @param not
*/
protected void doAdminSyncNotification(AgentId from, AdminSyncNotification not) {
int command = not.getCommand();
logmon.log(BasicLevel.WARN,
this.toString() + ".doAdminSyncNotification, no handling for command #" + command);
not.Return(new Object[] {"Unkonw command: " + command});
}
/**
* Called to inform this agent that it is garbaged and that it should free
* any active resources that it has allocated.
* A subclass of Agent
should override this method if it has
* any operation that it wants to perform before it is garbaged. For example,
* an agent with threads (a ProxyAgent for example) would use the initialize
* method to create the threads and the agentFinalize
method to
* stop them.
*
* Be careful, the notification sending is not allowed in this method.
*
* The implementation of this method provided by the Agent
class
* just unregister the JMX MBean if needed.
*
* @param lastTime true when last called by the factory on agent deletion.
*/
public void agentFinalize(boolean lastTime) {
try {
MXWrapper.unregisterMBean("AgentServer", getA3MBeanName());
} catch (Exception exc) {
if (logmon.isLoggable(BasicLevel.DEBUG))
logmon.log(BasicLevel.WARN,
"Agent" + id + " [" + name + "] JMX unregistration failed", exc);
else
logmon.log(BasicLevel.WARN,
"Agent" + id + " [" + name + "] JMX unregistration failed");
}
if (logmon.isLoggable(BasicLevel.DEBUG))
logmon.log(BasicLevel.DEBUG,
"Agent" + id + " [" + name + "], finalize: " + lastTime);
}
/* ************************************************************
* Encodable interface.
* ************************************************************/
/**
* Enables the sub classes not to implement this method.
* @return -1
*/
@Override
public int getEncodableClassId() {
return -1;
}
/**
* Returns the size of the encoded object.
* @return the size of the encoded object
* @exception Exception if an error occurs
*/
@Override
public int getEncodedSize() throws Exception {
int encodedSize = EncodableHelper.getNullableStringEncodedSize(name);
encodedSize += BOOLEAN_ENCODED_SIZE;
return encodedSize;
}
/**
* Encodes the object.
* @param encoder the encoder
* @exception Exception if an error occurs
*/
@Override
public void encode(Encoder encoder) throws Exception {
encoder.encodeNullableString(name);
encoder.encodeBoolean(fixed);
}
/**
* Decodes the object.
* @param decoder the encoder
* @exception Exception if an error occurs
*/
@Override
public void decode(Decoder decoder) throws Exception {
name = decoder.decodeNullableString();
fixed = decoder.decodeBoolean();
updated = true;
}
}