All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fr.dyade.aaa.agent.Agent Maven / Gradle / Ivy

There is a newer version: 5.22.0-EFLUID
Show newest version
/*
 * 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy