//
// Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s).
// All rights reserved.
//
package openadk.library;
import openadk.library.impl.*;
import openadk.library.impl.ObjectFactory.ADKFactoryType;
import openadk.library.log.ServerLog;
import openadk.library.reporting.ReportingDTD;
import openadk.library.threadpool.ThreadPoolManager;
import openadk.util.GUIDGenerator;
import org.apache.log4j.*;
import java.util.*;
import java.io.*;
/**
* The base class for a SIF Agent.<p>
*
* Derive your own class from this one and pass the <i>SourceId</i> to the
* superclass constructor. Call the <code>initialize</code> method to initialize
* the agent prior to use.<p>
*
*/
public class Agent
{
/// <summary>
/// The Identifier that is used to identify the ADK itself for logging operations ("ADK")
/// </summary>
/**
*
*/
public static final String LOG_IDENTIFIER = ADK.LOG_IDENTIFIER + ".Agent";
/**
* The root log Category. Subcategories exist for each zone, where the
* subcategory name is "ADK.Agent$<i>zoneId</i>". The ADK uses the root
* Category when writing log events that are not associated with a
* specific zone. Your agent may also use this Category to post log
* events.
*/
protected static Logger log = LogManager.getLogger( LOG_IDENTIFIER );
/**
* The root ServerLog instance. Subcategories exist for each zone, where
* the subcategory name is "ADK.Agent$<i>zoneId</i>". The Agent uses the
* root ServerLog instance only to establish the agent-global chain of
* loggers; no actual logging is performed outside the context of a zone.
*/
protected static ServerLog serverLog = ServerLog.getInstance( LOG_IDENTIFIER, null );
/**
* The agent's SourceId
*/
protected String fSourceId;
/**
* The display name used in SIF_Register/SIF_Name and in HTTP "UserAgent" headers
*/
protected String fName;
/**
* The ZoneFactory for this agent. Each agent has its own ZoneFactory in
* case the application vendor creates multiple Agent instances in the same
* virtual machine, a rare case but one that it supported
*/
private ZoneFactory fZoneFactory;
/**
* The TopicFactory for this agent. Each agent has its own TopicFactory in
* case the application vendor creates multiple Agent instances in the same
* virtual machine, a rare case but one that it supported
*/
private TopicFactory fTopicFactory;
/**
* The UndeliverableMessageHandler for the agent
*/
private UndeliverableMessageHandler fErrHandler;
/**
* Agent and default zone properties
*/
private AgentProperties fProps;
/**
* Initialization state
*/
private boolean fInit = false;
/**
* Is the agent in the process of shutting down?
*/
private boolean fShutdownInProgress = false;
/**
* Has the agent been shutdown?
*/
private boolean fShutdown = false;
/**
* The Object that configured this instance of the agent, e.g. an instance of AgentConfig
*/
private Object fConfigurationSource;
/**
* The Subscribers registered with this agent. The map is keyed by SIF
* data object names (e.g. "StudentPersonal"). If a Subscriber is registered
* for all object types, it is keyed by the ElementDef SIFDTD.SIF_MESSAGE.
*/
protected HashMap<ElementDef,Subscriber> fSubs = new HashMap<ElementDef,Subscriber>();
/**
* The Publishers registered with this agent. The map is keyed by SIF
* data object names (e.g. "StudentPersonal"). If a Publisher is registered
* for all object types, it is keyed by the ElementDef SIFDTD.SIF_MESSAGE.
*/
protected HashMap<ElementDef,Object> fPubs = new HashMap<ElementDef,Object>();
/**
* The QueryResults objects registered with this agent. The map is keyed by
* SIF data object names (e.g. "StudentPersonal"). If a QueryResults object
* is registered for all object types, it is keyed by the ElementDef SIFDTD.SIF_MESSAGE.
*/
protected HashMap<ElementDef,QueryResults> fQueryResults = new HashMap<ElementDef, QueryResults>();
/**
* Manages the MessagingListeners
*/
protected List<MessagingListener> fMessagingListeners = new ArrayList<MessagingListener>(0);
/**
* The TransportManager instances that manages all open transports for this agent
*/
private final TransportManagerImpl fTransportManager;
/**
* The thread pool shared across transports for pull mode
*/
private ThreadPoolManager fThreadPoolManager = null;
/**
* Constructor<p>
*
* @param agentId The string name that uniquely identifies this agent in SIF Zones.
* This string is used as the <code>SourceId</code> in all SIF message
* headers created by the agent.
*/
public Agent( String agentId )
{
if( agentId == null || agentId.trim().length() == 0 )
ADKUtils._throw( new IllegalArgumentException("Agent ID cannot be null or a blank string"), log );
fSourceId = agentId;
try
{
ObjectFactory factory = ObjectFactory.getInstance();
fZoneFactory = (ZoneFactory) factory.createInstance( ADKFactoryType.ZONE, this );
fTopicFactory = (TopicFactory) factory.createInstance( ADKFactoryType.TOPIC, this );
}
catch( ADKException objectFactoryEx ){
throw new RuntimeException( "Unable to create an instance of Agent: " + objectFactoryEx.getMessage(), objectFactoryEx );
}
fTransportManager = new TransportManagerImpl();
// Change the default LogSink that Jetty uses so that it is re-routed to
// the Log4j Category for the agent
//System.setProperty( "LOG_SINKS", "openadk.library.impl.Log4jLogSink" );
}
/**
* Gets the ZoneFactory for this agent. The ZoneFactory is used to create
* Zone instances to represent logical SIF zones. An application can also
* call ZoneFactory methods to enumerate the Zones that have been created.
* <p>
*
* @return The agent's ZoneFactory
*/
public ZoneFactory getZoneFactory() {
_checkInit();
return fZoneFactory;
}
/**
* Gets the TopicFactory for this agent. The TopicFactory is used to create
* Topic instances to aggregate publish and subscribe activity for a given
* type of SIF data object across one or more zones. An application can
* also call TopicFactory methods to enumerate the Topics that have been
* created.<p>
*
* @return The agent's TopicFactory
*/
public TopicFactory getTopicFactory() {
_checkInit();
return fTopicFactory;
}
/**
* Gets the properties for this agent.<p>
*
* The agent properties serve as defaults for new Zone objects created by
* the ZoneFactory. Properties may be customized on a zone-by-zone basis.
* If a property is not specified for a given zone, its value is inherited
* from the AgentProperties object returned by this method. Note this
* method returns the same object as getDefaultZoneProperties.<p>
*
* Agent properties should be set prior to calling the Agent.initialize
* method.<p>
*
* @return The agent properties
*/
public AgentProperties getProperties()
{
if( fProps == null )
{
fProps = new AgentProperties( this );
}
return fProps;
}
/**
* Gets the default properties for a transport protocol.<p>
*
* Each transport protocol supported by the ADK is represented by a class
* that implements the Transport interface. Transports are identified by
* a string such as "http" or "https". Like Zones, each Transport instance
* is associated with a set of properties specific to the transport
* protocol. Such properties may include IP address, port, SSL security
* attributes, and so on. The default properties for a given transport
* protocol may be obtained by calling this method.<p>
* @param protocol The protocol to get default properties for e.g. "http"
*
* @return The default properties for the specified protocol
*
* @exception ADKTransportException is thrown if the protocol is not supported
* by the ADK
*/
public TransportProperties getDefaultTransportProperties( String protocol )
throws ADKTransportException
{
return fTransportManager.getDefaultTransportProperties( protocol );
}
/**
* Convenience method to get the default HTTP transport properties
* @return the default HTTP transport properties
*/
public HttpProperties getDefaultHttpProperties() {
try {
return (HttpProperties)getDefaultTransportProperties("http");
} catch( ADKTransportException impossible ) {
throw new RuntimeException( impossible.getMessage(), impossible );
}
}
/**
* Convenience method to get the default HTTPS transport properties
* @return the default HTTPS transport properties
*/
public HttpsProperties getDefaultHttpsProperties() {
try {
return (HttpsProperties)getDefaultTransportProperties("https");
} catch( ADKTransportException impossible ) {
throw new RuntimeException( impossible.getMessage(), impossible );
}
}
/**
* Gets the default properties for Zones created by the ZoneFactory.
* @return The default zone properties
*/
public AgentProperties getDefaultZoneProperties()
{
return getProperties();
}
/**
* Gets the agent's <i>home directory</i>. By default, the home directory
* is the directory the agent was started from (if defined, the "adk.home"
* System property overrides this value.) You may override this method to
* return a home directory specific to your product's installation
* directory structure.<p>
*
* The Agent Runtime creates all work directories and files relative to
* the home directory. In some cases the Class Framework creates workspaces
* even when the Agent Runtime is not enabled.<p>
*
* @return The agent's home directory
*/
public String getHomeDir()
{
String dir = System.getProperty("adk.home");
if( dir != null )
return dir;
return System.getProperty("user.dir");
}
/**
* Gets the agent's SourceId
* @return The string name that uniquely identifies this agent in a SIF Zone.
*/
public String getId()
{
return fSourceId;
}
/**
* Sets the agent's SourceId
* @param sourceId A string name that uniquely identifies this agent in
* a SIF Zone. SIF does not specify any restrictions on the length or
* characters that may appear in the SourceId.
*/
public void setId( String sourceId )
{
fSourceId = sourceId;
if( BuildOptions.PROFILED )
{
Agent.log.debug( "SIF Profiling Harness support enabled in ADK" );
// Establish the SIFProfilerClient name (i.e. "sourceId_ADK")
ProfilerUtils.setProfilerName( getId() + "_ADK" );
}
}
/**
* Gets the descriptive name of the SIF Agent.<p>
*
* This string is used to identify the agent whenever a descriptive name is
* preferred over the agent ID. If the @link #getAgentInfo() method is not overrident,
* The class framework uses this string for the value of the <SIF_Name> element
* during agent registration.<p>
*
* By default, the agent ID is returned.<p>
*
* @return A descriptive name for the agent
*
* @see #getId
* @see #setName(String)
*/
public String getName()
{
if( fName == null )
return getId();
return fName;
}
/**
* Sets the descriptive name of the SIF Agent.<p>
*
* This string is used to identify the agent whenever a descriptive name is
* preferred over the agent ID. The class framework uses this string for the
* value of the <SIF_Name> element during agent registration.<p>
*
* By default, the agent ID is used as the descriptive name. This method
* must be called at agent initialization time prior to connecting to
* zones.<p>
*
* @param name A descriptive name for the agent
*
* @see #getName
*/
public void setName( String name )
{
fName = name;
}
/**
* Initialize the agent.<p>
*
* An application must call this method to initialize the class framework
* and runtime. No other methods can be called until the agent has been
* successfully initialized. When the agent exits it is important that the
* <code>shutdown</code> method be called to safely release the resources
* allocated by the runtime.<p>
*
* If an application overrides this method, it should call the superclass
* implementation <i>after</i> performing its own initialization.
* @throws Exception If the agent is unable to initialize due to a file
* or resource exception
* @exception ADKException is thrown if the agent has already
* been initialized
*
* @see #shutdown()
*/
public synchronized void initialize()
throws Exception
{
if( fInit )
ADKUtils._throw( new ADKException("Agent is already initialized",null),log );
if( fShutdownInProgress )
ADKUtils._throw( new ADKException("Agent is in the process of shutting down",null),log );
if( ( ADK.debug & ADK.DBG_LIFECYCLE ) != 0 )
log.info("Initializing agent...");
// Verify home directory exists and can be written to
File f = new File( getHomeDir() );
if( !f.exists() ) {
if( ( ADK.debug & ADK.DBG_LIFECYCLE ) != 0 )
log.debug("Creating home directory: " + f.getAbsolutePath() );
f.mkdirs();
}
if( !f.isDirectory() ) {
ADKUtils._throw( new ADKException( "The home directory is not a directory: " + f.getAbsolutePath(), null ), log );
}
// Verify work directory exists and can be written to
f = new File( getHomeDir() + File.separator + "work" );
if( ( ADK.debug & ADK.DBG_LIFECYCLE ) != 0 )
log.info( "Setting work directory to: " + f.getAbsolutePath() );
if( !f.exists() ) {
if( ( ADK.debug & ADK.DBG_LIFECYCLE ) != 0 )
log.debug("Creating work directory: " + f.getAbsolutePath() );
f.mkdirs();
}
if( !f.isDirectory() ) {
ADKUtils._throw( new ADKException( "The work directory is not a directory: " + f.getAbsolutePath(), null ), log );
}
if( BuildOptions.PROFILED )
{
System.out.println( "SIF Profiling Harness support enabled in ADK" );
// Establish the SIFProfilerClient name (i.e. "sourceId_ADK")
ProfilerUtils.setProfilerName( getId() + "_ADK" );
}
getProperties();
fThreadPoolManager = new ThreadPoolManager(fProps.getThreadingCorePoolSize(), fProps.getThreadingMaximumPoolSize(), fProps.getThreadingKeepAliveTime());
//Initialize the TransportManager
fTransportManager.activate( this );
fShutdownInProgress = false;
fInit = true;
if( log.isInfoEnabled() && ( ADK.debug & ADK.DBG_LIFECYCLE ) != 0 ){
log.info("Agent initialized");
}
}
public ThreadPoolManager getThreadPoolManager() {
return fThreadPoolManager;
}
/**
* Shutdown the agent.<p>
*
* This method should always be called before the application ends. It
* closes resources held by the class framework and runtime.<p>
*
* Calling this form of shutdown does not send any provisioning messages.
* @throws ADKException
*/
public synchronized void shutdown()
throws ADKException
{
shutdown(0);
}
/**
* Shutdown the agent.<p>
*
* This method should always be called before the application ends. It
* closes resources held by the ADK Class Framework and ADK Agent Runtime.
* <p>
*
* Provisioning messages are sent as follows:<p>
*
* <ul>
* <li>
* If the agent is using ADK-managed provisioning, a <code><
* SIF_Unregister></code> message is sent to each zone to which
* the agent is connected if the ADKFlags.PROV_UNREGISTER
* flag is specified. <code><SIF_Unsubscribe></code> and
* <code><SIF_Unprovide></code> messages are sent to each
* zone joined to a Topic when the ADKFlags.PROV_UNSUBSCRIBE
* and ADKFlags.PROV_UNPROVIDE flags are specified, respectively.
* When ADK-managed provisioning is disabled, no provisioning
* messages are sent to zones.
* </li>
* <li>
* If Agent-managed provisioning is enabled, the ProvisioningOptions
* flags have no affect. The agent must explicitly call the
* Zone.sifUnregister, Zone.sifUnsubscribe, and Zone.sifUnprovide
* methods to manually send those messages to each zone.
* </li>
* <li>
* If ZIS-managed provisioning is enabled, no provisioning messages
* are sent by the agent regardless of the ProvisioningOptions
* used and the methods are called.
* </li>
* </ul>
* <p>
* @param provisioningOptions The options from the ADKFlags constants
* @throws ADKException
*
* @see #initialize
*/
public synchronized void shutdown( int provisioningOptions )
throws ADKException
{
if( !fInit )
return;
fShutdownInProgress = true;
if( ( ADK.debug & ADK.DBG_LIFECYCLE ) != 0 )
log.info( "Shutting down agent..." );
// Close the SIFProfilerClient if supported
// if( BuildOptions.PROFILED ) {
// openadk.profiler.SIFProfilerClient prof =
// openadk.profiler.SIFProfilerClient.getInstance( ProfilerUtils.getProfilerName() );
// if( prof != null ) {
// try {
// prof.close();
// } catch( Exception ex ) {
// log.error( ex );
// }
// }
// }
try
{
// TODO: Unsubscribe, Unprovide topics
// Disconnect and shutdown each zone...
ZoneFactory zf = getZoneFactory();
Zone[] zones = zf.getAllZones();
for( int i = 0; i < zones.length; i++ ) {
zones[i].disconnect(provisioningOptions);
((ZoneImpl)zones[i]).shutdown();
}
if( fTransportManager != null )
{
// Shutdown transports
if( ( ADK.debug & ADK.DBG_LIFECYCLE ) != 0 ){
log.info("Shutting down Transports...");
}
fTransportManager.shutdown();
}
// Close RequestCache
try {
RequestCache rc = RequestCache.getInstance(this);
if( rc != null )
rc.close();
} catch( Exception ex ) {
}
if( ( ADK.debug & ADK.DBG_LIFECYCLE ) != 0 )
log.debug("Agent shutdown complete");
}
finally
{
fInit = false;
fShutdown = true;
}
}
/**
* Determines if the agent has been initialized
* @return true if the <code>initialize</code> method was called successfully;
* false if that method has not been called successfully or the agent
* has since been shut down.
*/
public boolean isInitialized()
{
return fInit;
}
/**
* Determines if the agent has been shutdown.<p>
*
* @return true if the <code>shutdown</code> method was called and the
* agent is either in the process of shutting down or has finished
* shutting down. Note the agent is considered to be shutdown even
* if the <code>shutdown</code> method fails.
*/
public boolean isShutdown()
{
return fShutdownInProgress || !fInit;
}
/**
* Puts all connected zones into sleep mode.<p>
*
* For each zone in the connected state, a SIF_Sleep message is sent to the
* Zone Integration Server to request that this agent's queue be put into
* sleep mode for that zone. If successful, the ZIS should not deliver
* further messages to this agent until it is receives a SIF_Register or
* SIF_Wakeup message from the agent. Note the ADK keeps an internal sleep
* flag for each zone, which is initialized when the <code>connect</code>
* method is called by sending a SIF_Ping to the ZIS. This flag is set so
* that the ADK will return a Status Code 8 ("Receiver is sleeping") in
* response to any message received by the ZIS for the duration of the
* session.
* <p>
*
* @exception ADKException thrown if the SIF_Sleep message is unsuccessful.
* The ADK will attempt to send a SIF_Sleep to all connected zones; the
* exception describes the zone or zones that failed
*/
public synchronized void sleep()
throws ADKException
{
_checkInit();
ADKMessagingException err = null;
Zone[] zones = fZoneFactory.getAllZones();
for( int i = 0; i < zones.length; i++ )
{
try
{
zones[i].sleep();
}
catch( ADKMessagingException ex )
{
if( err == null )
err = new ADKMessagingException("An error occurred sending SIF_Sleep to zone \""+zones[i].getZoneId()+"\"",zones[i]);
err.add(ex);
}
}
if( err != null )
ADKUtils._throw( err,log );
}
/**
* Wakes up all connected zones if currently in sleep mode.<p>
*
* For each connected zone, a SIF_Wakeup message is sent to the Zone
* Integration Server to request that sleep mode be removed from this agent's
* queue for that zone. Note the ADK keeps an internal sleep flag for each
* zone, which is initialized when the <code>connect</code> method is called
* by sending a SIF_Ping to the ZIS. This flag is cleared so that the ADK
* will no longer return a Status Code 8 ("Receiver is sleeping") in response
* to messages received by the ZIS.
* <p>
*
* @exception ADKException thrown if the SIF_Wakeup message is unsuccessful.
* The ADK will attempt to send a SIF_Wakeup to all connected zones; the
* exception describes the zone or zones that failed
*/
public synchronized void wakeup()
throws ADKException
{
_checkInit();
ADKMessagingException err = null;
Zone[] zones = fZoneFactory.getAllZones();
for( int i = 0; i < zones.length; i++ )
{
try
{
zones[i].wakeup();
}
catch( ADKMessagingException ex )
{
if( err == null )
err = new ADKMessagingException("An error occurred sending SIF_Wakeup to zone \""+zones[i].getZoneId()+"\"",zones[i]);
err.add(ex);
}
}
if( err != null )
ADKUtils._throw(err,log);
}
/**
* Register a global Publisher message handler with this agent for all SIF object types.<p>
*
* Note agents typically register message handlers with Topics or with Zones
* instead of with the Agent. The message dispatcher first
* delivers messages to Topics, then to Zones, and finally to the Agent
* itself.<p>
*
* In order to receive SIF_Request messages, the agent is expected to be
* registered as a Provider of one or more object types in at least one
* zone. This method does not send SIF_Provide messages to any zones.<p>
*
* @param publisher An object that implements the <code>Publisher</code>
* interface to respond to SIF_Request queries received by the agent.
* This object will be called whenever a SIF_Request is received by
* and no other object in the message dispatching chain has processed
* the message.
*/
public void setPublisher( Publisher publisher )
{
setPublisher(publisher,null);
}
/**
* Register a global Publisher message handler with the agent for the specified SIF object type.<p>
*
* Note agents typically register message handlers with Topics or with Zones
* instead of with the Agent. The message dispatcher first delivers messages
* to Topics, then to Zones, and finally to the Agent itself.<p>
*
* In order to receive SIF_Request messages, the agent is expected to be
* registered as a Provider of one or more object types in at least one
* zone. This method does not send SIF_Provide messages to any zones.<p>
*
* @param publisher An object that implements the <code>Publisher</code>
* interface to respond to SIF_Request queries received by the agent,
* where the SIF object type referenced by the request matches the
* specified objectType. This Publisher will be called whenever a
* SIF_Request is received and no other object in the message dispatching
* chain has processed the message.
*
* @param objectType A constant from the SIFDTD class that identifies the
* type of SIF Data Object this Publisher will respond to.
*/
public void setPublisher( Publisher publisher, ElementDef objectType )
{
if( publisher == null )
ADKUtils._throw( new IllegalArgumentException("Publisher object cannot be null"),Agent.getLog() );
if( objectType == null )
// Set the wildcard handler for all requests
fPubs.put( SIFDTD.SIF_MESSAGE, publisher );
else
{
if( objectType == ReportingDTD.SIF_REPORTOBJECT ){
throw new IllegalArgumentException("You must call setReportPublisher for SIF_ReportObject objects");
}
fPubs.put( objectType, publisher );
}
}
/**
* Register a global ReportPublisher message handler with the agent.<p>
*
* Note agents typically register message handlers with Topics or with Zones
* instead of with the Agent. The message dispatcher first delivers messages
* to Topics, then to Zones, and finally to the Agent itself.
*
* In order to receive SIF_Request messages, the agent is expected to be
* registered as a Provider of one or more object types in at least one
* zone. This method does not send SIF_Provide messages to any zones.<p>
*
* @param publisher An object that implements the <code>ReportPublisher</code>
* interface to respond to SIF_Request queries received for SIF_ReportObject
* objects. This Publisher will be called whenever a request is received
* and no other object in the message dispatching chain has processed
* the message.
*
* @since ADK 1.5
*/
public void setReportPublisher( ReportPublisher publisher )
{
if( publisher == null ){
ADKUtils._throw( new IllegalArgumentException("ReportPublisher object cannot be null"),Agent.getLog() );
}
if( ReportingDTD.SIF_REPORTOBJECT == null ){
ADKUtils._throw( new IllegalStateException("The ADK Reporting package is not loaded"),Agent.getLog() );
}
fPubs.put( ReportingDTD.SIF_REPORTOBJECT, publisher );
}
/**
* Register a global Subscriber message handler with the agent for all SIF object types.<p>
*
* Note agents typically register message handlers with Topics or with Zones
* instead of with the Agent. The message dispatcher first
* delivers messages to Topics, then to Zones, and finally to the Agent
* itself.<p>
*
* In order to receive SIF_Event messages, the agent is expected to be
* registered as a Subscriber of one or more object types in at least one
* zone. This method does not send SIF_Subscribe messages to any zones.<p>
*
* @param subscriber An object that implements the <code>Subscriber</code>
* interface to respond to SIF_Event notifications received by the agent.
* This object will be called whenever a SIF_Event is received and no
* other object in the message dispatching chain has processed the
* message.
*/
public void setSubscriber( Subscriber subscriber )
{
setSubscriber(subscriber,null);
}
/**
* Register a global Subscriber message handler with this agent for the
* specified SIF object type and only for the default SIF Context<p>
*
* Note agents typically register message handlers with Topics or with Zones
* instead of with the Agent. The message dispatcher first
* delivers messages to Topics, then to Zones, and finally to the Agent
* itself.<p>
*
* In order to receive SIF_Event messages, the agent is expected to be
* registered as a Subscriber of one or more object types in at least one
* zone. This method does not send SIF_Subscribe messages to any zones.<p>
*
* @see Provisioner#setSubscriber(Subscriber, ElementDef, SubscriptionOptions)
* @see Topic#setSubscriber(Subscriber, SubscriptionOptions)
*
* @param subscriber An object that implements the <code>Subscriber</code>
* interface to respond to SIF_Event notifications received by the agent,
* where the SIF object type referenced by the request matches the
* specified objectType. This Subscriber will be called whenever a
* SIF_Event is received and no other object in the message dispatching
* chain has processed the message.
*
* @param objectType A constant from the SIFDTD class that identifies the
* type of SIF Data Object this Subscriber will respond to.
*/
public void setSubscriber( Subscriber subscriber, ElementDef objectType )
{
if( subscriber == null )
ADKUtils._throw( new IllegalArgumentException("Subscriber object cannot be null"),Agent.getLog() );
if( objectType == null )
// Set the wildcard subscriber
fSubs.put( SIFDTD.SIF_MESSAGE, subscriber );
else
fSubs.put(objectType,subscriber);
}
/**
* Register a global QueryResults message handler with this agent for all
* SIF object types and only for the default SIF Context<p>
*
* Note agents typically register message handlers with Topics or with
* Zones instead of with the Agent. The message dispatcher first
* delivers messages to Topics, then to Zones, and finally to the Agent
* itself.<p>
*
* @see Provisioner#setQueryResults(QueryResults)
* @see Topic#setQueryResults(QueryResults, QueryResultsOptions)
*
* @param queryResults An object that implements the <code>QueryResults</code>
* interface to respond to SIF_Response query results received by the
* agent. This object will be called whenever a SIF_Response is received
* and no other object in the message dispatching chain has processed
* the message.
*/
public void setQueryResults( QueryResults queryResults )
{
setQueryResults( queryResults, null );
}
/**
* Register a global QueryResults message handler object with this agent for
* the specified SIF object type and only for the default SIF Context<p>
*
* Note agents typically register message handlers with Topics or with
* Zones instead of with the Agent. The message dispatcher first
* delivers messages to Topics, then to Zones, and finally to the Agent
* itself.<p>
*
* @see Provisioner#setQueryResults(QueryResults, ElementDef, QueryResultsOptions)
* @see Topic#setQueryResults(QueryResults, QueryResultsOptions)
*
* @param queryResults An object that implements the <code>QueryResults</code>
* interface to respond to SIF_Response query results received by the agent,
* where the SIF object type referenced by the request matches the
* specified objectType. This QueryResults object will be called whenever
* a SIF_Response is received and no other object in the message
* dispatching chain has processed the message.
*
* @param objectType A constant from the SIFDTD class that identifies the
* type of SIF Data Object this QueryResults message handler will
* respond to.
*/
public void setQueryResults( QueryResults queryResults, ElementDef objectType )
{
if( queryResults == null )
ADKUtils._throw( new IllegalArgumentException("QueryResults object cannot be null"),Agent.getLog() );
if( objectType == null )
// Set the wildcard query results handler
fQueryResults.put( SIFDTD.SIF_MESSAGE, queryResults );
else
fQueryResults.put( objectType, queryResults );
}
/**
* Gets the global Publisher message handler registered with the Agent for the
* specified SIF object type<p>
* @param context The SIF context to look up the Publisher handler for.
* The default implementation of Agent only returns handlers for
* SIFContext.DEFAULT
* @param objectType A SIFDTD constant identifying a SIF Data Object type
* (e.g. <code>SIFDTD.STUDENTPERSONAL</code>)
*
* @return The Publisher message handler registered for this object type by the
* agent when it called the <code>setPublisher</code> method, or <code>null</code> if
* no Publisher has been registered for the specified object type.
*/
public Publisher getPublisher( SIFContext context, ElementDef objectType )
{
Publisher p = null;
if( SIFContext.DEFAULT.equals( context ) ){
p =(Publisher)fPubs.get( objectType );
if( p == null ){
p = (Publisher)fPubs.get( SIFDTD.SIF_MESSAGE );
}
}
return p;
}
/**
* Gets the global ReportPublisher message handler registered with the Agent.<p>
*
* @param context The SIF context to look up the ReportPublisher handler for.
* The default implementation of Agent only returns handlers for
* SIFContext.DEFAULT
*
* @return The ReportPublisher message handler registered by the agent when
* it called the <code>setReportPublisher</code> method, or <code>null</code>
* if no ReportPublisher has been registered
*
* @since ADK 1.5
*/
public ReportPublisher getReportPublisher( SIFContext context )
{
if( !SIFContext.DEFAULT.equals( context ) ){
return null;
}
return (ReportPublisher)fPubs.get( ReportingDTD.SIF_REPORTOBJECT );
}
/**
* Gets the global Subscriber message handler registered with the Agent for
* the specified SIF object type.<p>
*
* @param context The SIF context to look up the Subscriber handler for.
* The default implementation of Agent only returns handlers for
* SIFContext.DEFAULT
*
* @param objectType A SIFDTD constant identifying a SIF Data Object type
* (e.g. <code>SIFDTD.STUDENTPERSONAL</code>)
*
* @return The Subscriber registered for this object type by the agent when
* it called the <code>setSubscriber</code> method, or <code>null</code>
* if no Subscriber has been registered for the specified object type.
*/
public Subscriber getSubscriber( SIFContext context, ElementDef objectType )
{
Subscriber s = null;
if( SIFContext.DEFAULT.equals( context ) ){
s = fSubs.get( objectType );
if( s == null ){
s = fSubs.get( SIFDTD.SIF_MESSAGE );
}
}
return s;
}
/**
* Gets the global QueryResults message handler registered with the Agent
* for the specified SIF object type.<p>
*
* @param context The SIF context to look up the QueryResults handler for.
* The default implementation of Agent only returns handlers for
* SIFContext.DEFAULT
*
* @param objectType A SIFDTD constant identifying a SIF Data Object type
* (e.g. <code>SIFDTD.STUDENTPERSONAL</code>)
*
* @return The QueryResults object registered for this object type by the
* agent when it called the <code>setQueryResults</code> method, or
* <code>null</code> if no QueryResults object has been registered
* for the specified object type.
*/
public QueryResults getQueryResults( SIFContext context, ElementDef objectType )
{
QueryResults q = null;
if( SIFContext.DEFAULT.equals( context ) ){
q = (QueryResults)fQueryResults.get(objectType.name());
if( q == null ){
q = (QueryResults)fQueryResults.get( SIFDTD.SIF_MESSAGE );
}
}
return q;
}
/**
* Register a <i>MessagingListener</i> to listen to messages received by the
* message handlers of this class.<p>
*
* NOTE: Agents may register a MessagingListener with the Agent or Zone
* classes. When a listener is registered with both classes, it will be
* called twice. Consequently, it is recommended that most implementations
* choose to register MessagingListeners with only one of these classes
* depending on whether the agent is interested in receiving global
* notifications or notifications on only a subset of zones.<p>
*
* @param listener a MessagingListener implementation
*/
public void addMessagingListener( MessagingListener listener )
{
fMessagingListeners.add( listener );
}
/**
* Remove a <i>MessagingListener</i> previously registered with the
* <code>addMessagingListener</code> method.<p>
*
* @param listener a MessagingListener implementation
*/
public void removeMessagingListener( MessagingListener listener )
{
fMessagingListeners.remove( listener );
}
/**
* Return a list of all MessagingListeners registered with the agent
* @return A list of MessagingListener objects
*/
public List<MessagingListener> getMessagingListeners()
{
return Collections.unmodifiableList( fMessagingListeners );
}
/**
* Purge all pending incoming and/or outgoing messages from this agent's
* queue. Affects all zones with which the agent is currently connected.
* See also the Topic.purgeQueue and Zone.purgeQueue methods to purge all
* zones associated with a topic or a specific zone, respectively.<p>
*
* <ul>
* <li>
* If the Agent Local Queue is enabled, messages are permanently
* and immediately removed from the queue. Any messages in transit
* are not affected.
* </li>
* <li>
* If the underlying messaging protocol offers a mechanism to clear
* the agent's queue, it is invoked. (SIF 1.0 does not have such a
* mechanism.)
* <li>
* Otherwise, all incoming messages received by the agent having a
* timestamp earlier than or equal to the time this method is called
* are discarded. This behavior persists until the agent is
* terminated or until a message is received having a later
* timestamp.
* </li>
* </ul>
*
* @param incoming true to purge incoming messages
* @param outgoing true to purge outgoing messages (e.g. pending SIF_Events)
* when the Agent Local Queue is enabled
* @throws ADKException
*/
public void purgeQueue( boolean incoming, boolean outgoing )
throws ADKException
{
}
/**
* Utility method to generate a GUID for SIF Data Objects and messages
* @return A GUID
* @see openadk.util.GUIDGenerator
*/
public static String makeGUID()
{
return GUIDGenerator.makeGUID();
}
/**
* Sets the <i>UndeliverableMessageHandler</i> to be called when a dispatching
* error occurs on a zone but no handler is registered with that zone. For
* more information, please refer to the UnderliverableMessageHandler
* class comments.<p>
*
* @param handler The handler to call when the ADK cannot dispatch an
* inbound message
*/
public void setErrorHandler( UndeliverableMessageHandler handler )
{
fErrHandler = handler;
}
/**
* Gets the <i>UndeliverableMessageHandler</i> for this agent.<p>
* @return The UndeliverableMessageHandler assigned to this agent
* @see #setErrorHandler
*/
public UndeliverableMessageHandler getErrorHandler()
{
return fErrHandler;
}
/**
* Gets the root Log4j Logger for this agent.
* @return The Logger for this agent
*/
public static Logger getLog()
{
return log;
}
/**
* Gets the Log4j Logger for a specific zone.
* @param zone The zone to get the logger for
* @return the Log4j Category for a specific zone.
*/
public static Logger getLog( Zone zone )
{
Logger zlog = Logger.getLogger( ADK.LOG_IDENTIFIER + ".Agent$" + zone.getZoneId() );
return zlog == null ? log : zlog;
}
/**
* Gets the agent-global ServerLog instance.<p>
*
* Agents that wish to customize server-side logging may call this
* method to obtain the global Agent ServerLog instance. Call any of the
* following methods to set up the chain of loggers that will be inherited
* by all Zones:<p>
*
* <ul>
* <li><code>addLogger</code></li>
* <li><code>removeLogger</code></li>
* <li><code>clearLoggers</code></li>
* <li><code>getLoggers</code></li>
* </ul>
*
* Unlike client-side logging, server logging requires a connection to a
* Zone Integration Server. Because the current SIF 1.x infrastructure does
* not allow connections to servers independent of a zone, the logging
* methods of ServerLog are useful only when called within the context of a
* zone. Therefore, calling any of the logging methods on the ServerLog
* instance returned by this method will result in an IllegalStateException.
* This method is provided only to set up the ServerLog logger chain at
* the global Agent level.<p>
*
* @return The agent's ServerLog instance
*
* @since ADK 1.5
*/
public static ServerLog getServerLog()
{
return serverLog;
}
/**
* Gets the ServerLog for a specific zone.<p>
*
* This form of <code>getServerLog</code> is provided for consistency with
* the <code>getLog</code> method. Note you may also call the
* <code>Zone.getServerLog</code> method directly to obtain a ServerLog for
* for a zone.<p>
*
* @param zone The zone to obtain a ServerLog instance for
* @return The ServerLog instance for the zone
*/
public static ServerLog getServerLog( Zone zone )
{
return zone.getServerLog();
}
/**
* Helper routine to check that the <code>initialize</code> method has been called
* @throws ADKException if not initialized
*/
private void _checkInit()
{
if( fShutdown )
throw new LifecycleException("Agent has been shutdown");
if( !fInit ) {
Thread.dumpStack();
ADKUtils._throw( new LifecycleException("Agent not initialized"), log );
}
}
/**
* Sets the source of this agent's configuration. This property can be
* used by other subsystems in the ADK to retrieve the configuration instance
* and pull additional configuration information.
* @param config The object that was used to configure the agent
*/
public void setConfigurationSource(Object config) {
fConfigurationSource = config;
}
/**
* Gets the source of this agent's configuration. For example, if AgentConfig is
* used to configure this instance of the agent, this property will return the
* AgentConfig object.
* @return The object that was used to configure the agent
*/
public Object getConfigurationSource(){
return fConfigurationSource;
}
/**
* Returns the TransportManager instance for this agent.
* TransportManager manages the state of all open transports
* known to this agent instance. The TransportManager will
* be null until {@link #initialize()} is called.
* @return the TransportManager instance for this agent.
*/
public final TransportManager getTransportManager() {
return fTransportManager;
}
}