// // Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s). // All rights reserved. // package openadk.library.impl; import java.util.List; import java.util.Vector; import java.util.Enumeration; import openadk.library.*; import openadk.library.reporting.ReportingDTD; import org.apache.log4j.Category; import org.apache.log4j.Logger; /** * Default implementation of the Topic interface. * * @author Eric Petersen * @version ADK 1.0 */ public class TopicImpl implements Topic { /** * Log4j logging category for this topic */ public Category log; /** * The Subscriber registered with this topic. */ protected Subscriber fSub; /** * The Publisher registered with this topic. */ protected Publisher fPub; /** * The ReportPublisher registered with this topic (for SIF_ReportObject topics only) */ protected ReportPublisher fReportPub; /** * The QueryResults registered with this topic. */ protected QueryResults fQueryResults; /** * The SIF data object type associated with this topic */ protected ElementDef fObjType; /** * The options for Publishing */ protected PublishingOptions fPubOpts; /** * The options for ReportPublishing */ protected ReportPublishingOptions fReportPubOpts; protected SubscriptionOptions fSubOpts; /** * The SIF Context that this topic is joined to */ private SIFContext fContext; /** * The Zones joined with this topic */ protected List<Zone> fZones = new Vector<Zone>(); /** * The options for QueryResults handling */ protected QueryResultsOptions fQueryResultsOptions; protected TopicImpl( ElementDef objType, SIFContext context ) { fObjType = objType; fContext = context; log = Logger.getLogger( Agent.LOG_IDENTIFIER + ".Topic$"+objType.name()); } /** * Adds a zone to this topic * @param zone The Zone to join with this topic * @exception ADKException is thrown if the zone is already joined to a * topic or if there is a SIF error during agent registration. */ public synchronized void join( Zone zone ) throws ADKException { // Check that zone is not already joined with this topic if( fZones.contains( zone ) ) ADKUtils._throw( new IllegalStateException("Zone already joined with topic \""+fObjType+"\""),((ZoneImpl)zone).log ); // Check that topic has a Provider, Subscriber, or QueryResults object if( fSub == null && fPub == null && fReportPub == null && fQueryResults == null ) ADKUtils._throw( new IllegalStateException("Agent has not registered a Subscriber, Publisher, or QueryResults object with this topic"),((ZoneImpl)zone).log ); fZones.add( zone ); if( zone.isConnected() ) ((ZoneImpl)zone).provision(); } /** * Gets the name of the SIF data object type associated with this topic * @return The name of a root level SIF data object such as "StudentPersonal", * "BusInfo", or "LibraryPatronStatus" */ public String getObjectType() { return fObjType.name(); } /* (non-Javadoc) * @see openadk.library.Topic#getObjectDef() */ public ElementDef getObjectDef() { return fObjType; } /* (non-Javadoc) * @see openadk.library.Topic#getSIFContext() */ public SIFContext getSIFContext() { return fContext; } /** * Gets the zones to which this topic is bound * @return The zone that created this topic instance */ public synchronized Zone[] getZones() { Zone[] arr = new Zone[ fZones.size() ]; fZones.toArray( arr ); return arr; } /** * Checks that at least one zone is joined with the topic * @exception ADKException is thrown if no zones are joined with this topc */ private void _checkZones() throws ADKException { if( fZones.size() == 0 ) throw new ADKException("No zones are joined with the \"" + fObjType + "\" topic",null); } /** * Register a publisher of this topic.<p> * * Provisioning messages are sent as follows:<p> * * <ul> * <li> * If the agent is using ADK-managed provisioning, a <code>< * SIF_Provide></code> message is sent to the ZIS when the * ADKFlags.PROV_PROVIDE flag is specified. When * ADK-managed provisioning is disabled, no messages are sent to * the zone. * </li> * <li> * If Agent-managed provisioning is enabled, the ProvisioningOptions * flags have no affect. The agent must explicitly call the * sifProvide method to manually send those message to the 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 publisher An object that implements the <code>Publisher</code> * interface to publish change events and to evaluate SIF queries * received by the agent */ public synchronized void setPublisher( Publisher publisher ) { setPublisher( publisher, null ); } /** * Register a publisher of this topic.<p> * * Provisioning messages are sent as follows:<p> * * <ul> * <li> * If the agent is using ADK-managed provisioning, a <code>< * SIF_Provide></code> message is sent to the ZIS when the * ADKFlags.PROV_PROVIDE flag is specified. When * ADK-managed provisioning is disabled, no messages are sent to * the zone. * </li> * <li> * If Agent-managed provisioning is enabled, the ProvisioningOptions * flags have no affect. The agent must explicitly call the * sifProvide method to manually send those message to the 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 publisher An object that implements the <code>Publisher</code> * interface to publish change events and to evaluate SIF queries * received by the agent * @param provisioningOptions The options to use for this Publisher */ public synchronized void setPublisher( Publisher publisher, PublishingOptions provisioningOptions ) { assertProvisioningOptions( provisioningOptions ); if( ( fObjType == ReportingDTD.SIF_REPORTOBJECT ) ){ throw new IllegalArgumentException( "You must call the setReportPublisher method for SIF_ReportObject topics" ); } if( publisher == null ){ fPub = null; fPubOpts = null; } else { fPub = publisher; if( provisioningOptions == null ){ provisioningOptions = new PublishingOptions(); } fPubOpts = provisioningOptions; } } /** * Register a ReportPublisher message handler to process SIF_Request messages * received for SIF_ReportObject objects from the zones joined with this topic. * ReportPublisher is implemented by Vertical Reporting applications that * publish report data via the SIF_ReportObject object (SIF 1.5 and later).<p> * * This method must be called instead of <code>setPublisher</code> for topics * created to represent the SIF_ReportObject data type.<p> * * @param publisher An object that implements the <code>Publisher</code> interface. * */ public synchronized void setReportPublisher( ReportPublisher publisher ) { setReportPublisher( publisher, null ); } /** * Register a ReportPublisher message handler to process SIF_Request messages * received for SIF_ReportObject objects from the zones joined with this topic. * ReportPublisher is implemented by Vertical Reporting applications that * publish report data via the SIF_ReportObject object (SIF 1.5 and later).<p> * * This method must be called instead of <code>setPublisher</code> for topics * created to represent the SIF_ReportObject data type.<p> * * @param publisher An object that implements the <code>Publisher</code> interface. * * @param flags Specify <code>ADKFlags.PROV_PROVIDE</code> to register the * agent as the default provider of the object type associated with this * topic. The ADK will send a SIF_Provide message to each zone joined * with the topic. Specify <code>ADKFlags.PROV_NONE</code> if the agent * can respond to requests for the object type but will not register as * the authoritative provider. * * @since ADK 1.5 */ public synchronized void setReportPublisher( ReportPublisher publisher, ReportPublishingOptions flags ) { assertProvisioningOptions( flags ); if( !(fObjType == ReportingDTD.SIF_REPORTOBJECT) ) { throw new IllegalArgumentException( "You must call the setPublisher method for topics that do not represent SIF_ReportObject" ); } if( publisher == null ){ fReportPub = null; fReportPubOpts = null; } else { fReportPub = publisher; if( flags == null ){ flags = new ReportPublishingOptions(); } fReportPubOpts = flags; } } public synchronized Publisher getPublisher() { return fPub; } public synchronized ReportPublisher getReportPublisher() { return fReportPub; } public synchronized void setSubscriber( Subscriber subscriber ) { setSubscriber(subscriber, null ); } public synchronized void setSubscriber( Subscriber subscriber, SubscriptionOptions flags ) { assertProvisioningOptions( flags ); if( subscriber == null ){ fSub = null; fSubOpts = null; } else { fSub = subscriber; if( flags == null ){ flags = new SubscriptionOptions(); } fSubOpts = flags; } } public synchronized Subscriber getSubscriber() { return fSub; } public synchronized void setQueryResults( QueryResults results ) throws ADKException { setQueryResults( results, null ); } public synchronized void setQueryResults( QueryResults results, QueryResultsOptions flags ) throws ADKException { assertProvisioningOptions( flags ); if( results == null ){ fQueryResults = null; fQueryResultsOptions = null; } else { fQueryResults = results; if( flags == null ){ flags = new QueryResultsOptions(); } fQueryResultsOptions = flags; } } public synchronized QueryResults getQueryResultsObject() { return fQueryResults; } private void assertProvisioningOptions( ProvisioningOptions opts ){ if( opts != null && opts.getSupportedContexts().size() > 1 ) { throw new IllegalArgumentException( "Cannot provision a single topic for more than one SIF Context.\r\n" + "To use Topics with multiple SIF contexts, call TopicFactory.getInstance( ElementDef, SIFContext )." ); } } /** * Publishes a change in topic data by sending a SIF_Event to all zones * joined with this topic<p> * * This method is useful for communicating a single change event. If an * agent changes data that spans several object types, it should consider * using the BatchEvent class to publish changes as a group. BatchEvent * aggregates changes in multiple SIF data objects, then sends a single * SIF_Event message to each zone. This is much more efficient than calling * the publishChange method of each Topic, which results in a single * SIF_Event message being sent for each object type. Another alternative * is to call the publishChange method of each Zone directly. That method * accepts an Event object, which can describe changes in multiple data * objects.<p> * * @param data The data that has changed. The objects in this array must all * be of the same SIF object type (e.g. all <code>StudentPersonal</code> * objects if this topic encapsulates the "StudentPersonal" object type), * and must all communicate the same state change (i.e. all added, * all changed, or all deleted). * @throws ADKException */ public synchronized void publishEvent( Event data ) throws ADKException { ADKMessagingException err = null; _checkZones(); for( Zone zone : fZones ) { ZoneImpl z = (ZoneImpl)zone; try { z.fPrimitives.sifEvent(z,data,null,null); } catch( Throwable th ) { if( err == null ) err = new ADKMessagingException("Error publishing event to topic \""+fObjType+"\"",z); if( th instanceof ADKException ) err.add( th ); else err.add( new ADKMessagingException(th.toString(),z) ); } } if( err != null ) ADKUtils._throw( err, log ); } /* (non-Javadoc) * @see openadk.library.Topic#createQuery() */ public Query createQuery() { return new Query( fObjType ); } /* (non-Javadoc) * @see openadk.library.Topic#query() */ public void query() throws ADKException { query( createQuery() ); } public synchronized void query( Query query ) throws ADKException { query(query,null,null,0); } public synchronized void query( Query query, MessagingListener listener ) throws ADKException { query(query,listener,null,0); } public synchronized void query( Query query, int queryOptions ) throws ADKException { query(query,null,null,queryOptions); } public synchronized void query( Query query, MessagingListener listener, int queryOptions ) throws ADKException { query(query,listener,null,queryOptions); } public synchronized void query( Query query, String destinationId, int queryOptions ) throws ADKException { query( query, null, destinationId, queryOptions ); } public synchronized void query( Query query, MessagingListener listener, String destinationId, int queryOptions ) throws ADKException { if( query == null ){ ADKUtils._throw( new IllegalArgumentException("Query object cannot be null"), log ); } // Validate that the query object type and SIF Context are valid for this Topic if( query.getObjectType() != fObjType ){ ADKUtils._throw( new IllegalArgumentException("Query object type: {" + query.getObjectTag() + "} does not match Topic object type: " + fObjType + "}"), log ); } if( !query.getSIFContext().equals( fContext ) ){ ADKUtils._throw( new IllegalArgumentException("Query SIF_Context: {" + query.getSIFContext() + "} does not match Topic SIF_Context: " + fContext + "}"), log ); } _checkZones(); ADKMessagingException err = null; // Send the SIF_Request to each zone for( Zone zone: fZones) { ZoneImpl z = (ZoneImpl)zone; try { z.query(query,listener,destinationId,queryOptions); } catch( Throwable th ) { if( err == null ) err = new ADKMessagingException("Error querying topic \"" + fObjType.name() + "\"",z); if( th instanceof ADKException ) err.add( th ); else err.add( new ADKMessagingException(th.toString(),z) ); } } if( err != null ) ADKUtils._throw( err, log ); } public void purgeQueue( boolean incoming, boolean outgoing ) throws ADKException { } public String toString() { return "Topic:" + fObjType.name(); } }