// // Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s). // All rights reserved. // package openadk.library; import java.util.List; import java.util.Vector; import openadk.library.impl.ADKUtils; import openadk.library.impl.SMBHelper; import openadk.library.impl.TrackQueryResultsImpl; /** * Synchronously issues one or more SIF Requests to a zone or topic and returns * the results to the calling thread as they are received.<p> * * TrackQueryResults differs from normal (asynchonous) SIF Request and Response * message handling in that responses are not dispatched to the usual QueryResults * objects in the message dispatching chain. Rather, they are delvered directly * to this class and returned to the calling thread by the <code>next</code> * method. The calling thread is blocked when <code>waitForResults</code> is * called for <i>synchronous</i> SIF Request processing.<p> * * <b>When to Use</b> * <p> * TrackQueryResults should be used when:<p> * * <ul> * <li> * Your agent needs to request one or more objects from a zone before * proceeding with the current operation. In this case, a synchronous * method of SIF Request and Response processing is ideal because the * current thread can block until the query has completed, avoiding * complex asynchronous interactions in your code. If results are not * received in the specified period of time, the agent can timeout and * abandon the operation in progress.<br/><br/> * </li> * <li> * Your agent needs to query a zone for objects while handling a SIF * Event message. In this case, Selective Message Blocking is * automatically invoked by the TrackQueryResults constructor. (Refer * to the SIF Specification for more information on Selective Message * Blocking.) Note: When the Agent Local Queue (ALQ) is enabled, * Selective Message Blocking is not necessary. The ADK will dispatch * messages from the local queue regardless. However, to ensure your * code works properly even when the local queue is disabled, we suggest * always using the TrackQueryResults class when sending SIF Requests * during SIF Event handling.<br/><br/> * </li> * </ul> * <p> * * <b>How to Use</b> * <p> * * Follow these steps to use TrackQueryResults:<p> * * <ul> * <li> * Create an instance of the TrackQueryResults class. If your code * is processing a SIF Event message from the Subscriber.onEvent * method, pass the <code>Event</code> object to the constructor. This * will invoke Selective Message Blocking on the event to prevent a * deadlock condition at the Zone Integration Server.<br/><br/> * * <code> * TrackQueryResult tqr = new TrackQueryResults();<br/> * </code> * <br/> * </li> * <li> * Call the <code>addQuery</code> method one or more times to specify * the queries the TrackQueryResults object will issue. If you add more * than one query, keep in mind the results may not be received in the * same order as the queries were issued.<br/><br/> * * <code> * // Query for a SchoolInfo object by RefId<br/> * SIFDTD dtd = ADK.DTD();<br/> * Query query = new Query( dtd.SCHOOLINFO );<br/> * query.addCondition( dtd.SCHOOLINFO_REFID, Condition.EQ, refId );<br/> * Topic schoolInfo = getTopicFactory().getInstance( dtd.SCHOOLINFO );<br/> * <br/> * // Add the query to TrackQueryResults<br/> * tqr.addQuery( schoolInfoTopic, query );<br/> * </code> * <br/> * </li> * <li> * Create a loop that calls the <code>waitForResults</code>, * <code>available</code>, and <code>next</code> methods as illustrated * below to wait for and retrieve the results of all queries. The * outermost <code>waitForResults</code> loop blocks until at least * <i>one</i> result is available, all queries have completed (i.e. the * last packet of each SIF_Response message has been received), or the * timeout period has expired. The inner <code>available</code> method * does not block.<br/><br/> * * <code> * // Timeout if results are not received in 30 seconds...<br/> * while( tqr.waitForResults( 30000 ) )<br/> * {<br/> *     // Retrieve all messages so far<br/> *     while( tqr.available() )<br/> *     {<br/> *         SIFDataObject[] data = tqr.next();<br/> *         for( int i = 0; i < data.length; i++ )<br/> *         {<br/> *             // Process the SIF Data Objects received...<br/> *         }<br/> *     }<br/> * }<br/> * </code> * <br/><br/> * </li> * </ul> * * <b>TrackQueryResultsData</b> * <p> * When TrackQueryResults receives a SIF Response message, it captures not only * the SIF Data Objects provided by that message, but also the zone from which * the message was received and the associated SIFMessageInfo object, which * includes header fields and optionally the XML content of the message. * TrackQueryResults packages this data to the caller by returning an instance * of the TrackQueryResultsData class.<p> * * @author Eric Petersen * @version ADK 1.0 */ public class TrackQueryResults extends TrackQueryResultsImpl { /** Handles calling back into MessageDispatcher's EvDisp thread to invoke SMB */ protected SMBHelper fSmb; /** The last timeout value passed to waitForResults */ protected int fTimeout = 0; /** Query options (not currently used) */ protected int fQueryOpts = 0; /** True if all queries have completed */ protected boolean fCompleted = false; /** Query errors */ protected Vector<ADKException> fErrors = new Vector<ADKException>(); /** * Constructs a TrackQueryResults object to be used when the agent is not * processing a SIF_Event message. If the agent is processing a SIF_Event * in the Subscriber.onEvent method, the other form of constructor must be * used to invoke Selective Message Blocking. */ public TrackQueryResults() throws ADKException { this( null,null ); } /** * Constructs a TrackQueryResults object to be used when the agent needs * to query for additional data while processing a SIF_Event message. * The Event object passed to Subscriber.onEvent must be passed to this * form of constructor. Selective Message Blocking is invoked when the * Agent Local Queue is not being used. * * @param event The Event object passed to Subscriber.onEvent * @param msgInfo The MessageInfo object passed to Subscriber.onEvent */ public TrackQueryResults( Event event, MessageInfo msgInfo ) throws ADKException { super(); if( event != null && event.getZone() == null ) ADKUtils._throw( new IllegalStateException("TrackQueryResults( Event ) can only be called during the processing of an incoming SIF_Event"), Agent.getLog() ); if( event != null ) { fSmb = new SMBHelper(this,event,msgInfo); fSmb.invokeSMB(); } } /** * Sets optional query options */ public void setQueryOptions( int queryOptions ) { fQueryOpts = queryOptions; } /** * Add a query to be executed when the waitForResults method is called */ public synchronized void addQuery( Zone zone, Query query ) { _addQuery( zone,query,null ); } /** * Add a query to be executed when the waitForResults method is called */ public synchronized void addQuery( Zone zone, Query query, String destinationId ) { _addQuery( zone,query,destinationId ); } /** * Add a query to be executed when the waitForResults method is called. */ public synchronized void addQuery( Topic topic, Query query ) { _addQuery( topic,query,null ); } /** * Add a query to be executed when the waitForResults method is called. */ public synchronized void addQuery( Topic topic, Query query, String destinationId ) { _addQuery( topic,query,destinationId ); } private void _addQuery( Object zoneOrTopic, Query query, String destinationId ) { // Let the ADK MessageDispatcher know which object to call onQueryPending on sRequestQueries.put( query, this ); fQueries.addElement( new QueryWrapper( query, zoneOrTopic, destinationId ) ); } /** * Sends a SIF_Request message for each Query passed to the <code>addQuery</code> * method and resets the timeout period to the given value. Blocks until at least * one pending query returns data or the timeout period expires.<p> * * This method should be called repeatedly in a <b>while</b> loop as * SIF_Response packets may be received by the agent in multiple packets. * If not enclosed in a <b>while</b> loop, your agent will only process * the first packet when a multi-packet SIF_Response is received. (If the * caller only expects one packet - for example, when querying for a single * SIF Data Object during SIF_Event processing - an outer <b>while</b> * loop is not necessary.) Each time this method is called, the timeout * period is reset and any new queries that have been added are executed. * If called and all queries tracked by this object have returned data, no * action is taken.<p> * * If a SIF_Request messages fails with an error acknowledgement from the * ZIS or otherwise results in an exception, the error condition can be * learned by calling the <code>getErrors</code> method. It is recommended * that you always check for an error condition by calling the <code>hasErrors</code> * or <code>getErrors</code> methods. * * @return true if result data is available, otherwise false * * @see #getErrors * @see #hasErrors */ public synchronized boolean waitForResults( int timeout ) { fTimeout = timeout; int sent = 0; int completed = 0; synchronized( fQueries ) { for( int i = 0; i < fQueries.size(); i++ ) { QueryWrapper w = (QueryWrapper)fQueries.elementAt(i); if( w.fRequestMsgId == null ) { fLastQuery = w; try { if( w.fZoneOrTopic instanceof Zone ) ((Zone)w.fZoneOrTopic).query( w.fQuery, w.fDestinationId, fQueryOpts ); else if( w.fZoneOrTopic instanceof Topic ) ((Topic)w.fZoneOrTopic).query( w.fQuery, w.fDestinationId, fQueryOpts ); } catch( ADKException ex ) { fErrors.addElement( ex ); completed++; } sent++; } else if( w.fCompleted != 0 ) completed++; } } // Any pending queries? fCompleted = ( completed == fQueries.size() ); if( fCompleted ) { return false; } // Wait for results to come in try { if( !fCompleted && fResults.size() == 0 ) { synchronized( this ) { wait( timeout ); } } } catch( InterruptedException ie ) { return false; } return fResults.size() > 0; } /** * Returns the number of results currently available * @return The number of */ public int available() { synchronized( fResults ) { return fResults.size(); } } /** * Determines if any results will be returned by the <code>next</code> method */ public boolean hasNext() { synchronized( fResults ) { return fResults.size() > 0; } } /** * Have all requests been received? */ public synchronized boolean isComplete() { return fCompleted; } /** * Returns the next group of result data, or null if no data is available. * This method must be called repeatedly to retrieve all results because * TrackQueryResults may have received multiple SIF_Response messages in * response to its queries.<p> */ public synchronized TrackQueryResultsData next() { synchronized( fResults ) { for( int i = 0; i < fResults.size(); i++ ) { TrackQueryResultsData data = (TrackQueryResultsData)fResults.elementAt(i); fResults.removeElementAt(i); return data; } } return null; } /** * Gets any SIF_Request errors received when the queries were executed.<p> * * When the <code>waitForResults</code> method is called, it executes all * queries previously added to the TrackQueryResults object. If an error * acknowledgement is received for a SIF_Request message, it is returned * in this array. Call the <code>ADKException.getZone</code> method to * learn the zone associated with each exception. * * @return An array of ADKExceptions representing all error acknowledgements * received by TrackQueryResults when the queries were issued. * * @see #hasErrors * @see #waitForResults */ public synchronized ADKException[] getErrors() { ADKException[] arr = new ADKException[ fErrors.size() ]; fErrors.copyInto( arr ); return arr; } /** * Determines if any SIF_Request errors occurred when the queries were executed.<p> * * @see #getErrors * @see #waitForResults */ public synchronized boolean hasErrors() { return fErrors.size() > 0; } }