// // Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s). // All rights reserved. // package openadk.library.impl; import java.io.IOException; import java.io.Serializable; import java.io.StringWriter; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map.Entry; import javax.xml.stream.XMLStreamException; import openadk.library.ADK; import openadk.library.ADKException; import openadk.library.ADKFlags; import openadk.library.ADKMessagingException; import openadk.library.ADKNotSupportedException; import openadk.library.ADKParsingException; import openadk.library.ADKQueueException; import openadk.library.ADKTransportException; import openadk.library.ADKZoneNotConnectedException; import openadk.library.AgentProperties; import openadk.library.Condition; import openadk.library.DataObjectOutputStream; import openadk.library.ElementDef; import openadk.library.Event; import openadk.library.MessageInfo; import openadk.library.MessagingListener; import openadk.library.Publisher; import openadk.library.Query; import openadk.library.QueryResults; import openadk.library.RawMessageListener; import openadk.library.ReportObjectOutputStream; import openadk.library.ReportPublisher; import openadk.library.RequestInfo; import openadk.library.SIFContext; import openadk.library.SIFDTD; import openadk.library.SIFDataObject; import openadk.library.SIFElement; import openadk.library.SIFErrorCategory; import openadk.library.SIFErrorCodes; import openadk.library.SIFException; import openadk.library.SIFMessageInfo; import openadk.library.SIFMessagePayload; import openadk.library.SIFMessagingListener; import openadk.library.SIFParser; import openadk.library.SIFStatusCodes; import openadk.library.SIFVersion; import openadk.library.SIFWriter; import openadk.library.Subscriber; import openadk.library.Topic; import openadk.library.TrackQueryResults; import openadk.library.UndeliverableMessageHandler; import openadk.library.Zone; import openadk.library.infra.AuthenticationLevel; import openadk.library.infra.EncryptionLevel; import openadk.library.infra.InfraDTD; import openadk.library.infra.SIF_Ack; import openadk.library.infra.SIF_Body; import openadk.library.infra.SIF_CancelRequests; import openadk.library.infra.SIF_Error; import openadk.library.infra.SIF_Event; import openadk.library.infra.SIF_EventObject; import openadk.library.infra.SIF_ExtendedQueryResults; import openadk.library.infra.SIF_GetMessage; import openadk.library.infra.SIF_Header; import openadk.library.infra.SIF_LogEntry; import openadk.library.infra.SIF_LogEntryHeader; import openadk.library.infra.SIF_ObjectData; import openadk.library.infra.SIF_Query; import openadk.library.infra.SIF_QueryObject; import openadk.library.infra.SIF_Request; import openadk.library.infra.SIF_RequestMsgId; import openadk.library.infra.SIF_Response; import openadk.library.infra.SIF_SecureChannel; import openadk.library.infra.SIF_Security; import openadk.library.infra.SIF_ServiceInput; import openadk.library.infra.SIF_ServiceNotify; import openadk.library.infra.SIF_ServiceOutput; import openadk.library.infra.SIF_Status; import openadk.library.infra.SIF_SystemControl; import openadk.library.infra.SIF_SystemControlData; import openadk.library.infra.SIF_Version; import openadk.library.infra.SIF_ZoneStatus; import openadk.library.policy.PolicyManager; import openadk.library.reporting.ReportingDTD; import openadk.library.services.SIFZoneService; import openadk.library.services.SIFZoneServiceProxy; import openadk.library.services.impl.ServiceOutputFileStream; import openadk.library.services.impl.ServiceOutputStreamImpl; import openadk.util.ADKStringUtils; import openadk.util.GUIDGenerator; /** * Handles message dispatching within the class framework.<p> * * There is a MessageDispatcher object for each zone. It is protocol-independent: * the MessageDispatcher works in conjunction with either the zone's protocol * handler (IProtocolHandler) or its Agent Local Queue (IAgentQueue) to produce * and consume messages, but never participates in the actual sending or * receiving of messages on the wire. Conversely, the protocol handler and * queue never dispatch messages; they are only concerned with sending and * receiving them. * <p> * * Message dispatching is at the heart of the class framework and the most * complex implementation class because of the multiple ways in which an agent * can exchange messages with a ZIS -- namely, Push and Pull modes, Selective * Message Blocking, and optional Agent Local Queue (with negates the need for * SMB when enabled). Further complicating matters is the fact that an agent * may be connected to multiple zones, some of which may be using Push mode * while others are using Pull mode. * <p> * * <b>Message Acknowledgement</b><p> * * SIF guarantees the delivery (and hopefully the processing) of a message by * requiring that it remain in its queue until acknowledged by the recipient. * An agent should only acknowledge a message after it has processed it, so the * ADK does not send acknowledgements when a message is received but rather * after it has been successfully dispatched without exception. In other words, * a message is not acknowledged simply because it was received successfully; * it must be processed by the agent first. This is an important distinction * to keep in mind and critical to the operation of MessageDispatcher. * <p> * * <b>Message Consumption</b><p> * * Throughout the rest of this commentary, ALQ refers to "Agent Local Queue" * and PH refers to "Protocol Handler".<p> * * MessageDispatcher consumes messages from one of two sources: either the ALQ * or a zone's PH. When the ALQ is enabled, the PH stores messages in the queue * as they are received from the network. It immediately acknowledges them * because they are safely persisted in the ALQ and will remain there even if * the agent goes down. Further, Selective Message Blocking is not needed when * the ALQ is enabled, so there is no reason to ever send anything but an * Immediate acknowledgement to the originating zone.<p> * * When the ALQ is disabled, however, messages must be dispatched and processed * before the PH can return an acknowledgement to the zone. This means a full * cycle through the class framework from the time a message is received by * the PH until the time it has been processed and a SIF_Ack is returned. To * complicate matters, an agent may need to invoke Selective Message Blocking * because the ALQ is not available, and in this case the framework may need * to return an Intermediate ack before the agent has had a chance to fully * process the message.<p> * * Thus, when ALQ is enabled MessageDispatcher runs in a thread to consume * messages from the queue as follows:<p> * * <ul> * <li>Waits for the next message to become available (blocks)</li> * <li>Dispatches the message to the appropriate Subscriber, Publisher, * or QueryResults object</li> * <li>If an exception is thrown during the dispatch call, the message is * left in the ALQ and the process repeats</li> * <li>If the dispatch call succeeds, the message is permanently removed * from the ALQ</li> * </ul> * * Remember, each message in the ALQ was acknowledged at the time it was placed * into the queue by the PH so the above algorithm never sends SIF_Ack messages. * It is purely concerned with dispatching messages waiting in the queue.<p> * * When ALQ is disabled MessageDispatcher does not run in a thread. Rather * than <i>consume</i> messages, it is handed them by the PH for synchronous * processing as they're received from the network. This process works as * follows:<p> * * <ul> * <li>The PH receives a message and calls MessageDispatcher.dispatch * to dispatch it</li> * <li>The message is dispatched to the appropriate Subscriber, Publisher, * or QueryResults object</li> * <li>If an exception is thrown during the dispatch call, the exception * is propagated up the call stack to the PH, which will respond by * simply not sending an acknowledgement. Thus, the message is left in * the agent queue on the zone server.</li> * <li>If the dispatch call succeeds, it returns a status code 1, 2, or 3 * to the PH and the PH sends an acknowledgement with that status code. * These codes corresponding to the Immediate, Intermediate, and Final * acknowledgement types. Status code 2 (Intermediate) is only returned * if the message was dispatched to a Subscriber. Status code 3 (Final) * is only returned if the message was dispatched to a QueryResults * object in response to an earlier SIF_Event that invoked Selective * Message Blocking. Status code 1 (Immediate) is returned in all other * cases. Note MessageDispatcher <b>does not</b> keep track of SMB * state; this is the job of the dispatch recipient.</li> * </ul> * * <b>Message Production</b><p> * * MessageDispatcher also "produces" messages on behalf of Topic and Zone * objects when posting outgoing SIF_Event, SIF_Request, and SIF_Response * messages (these are the only outgoing infrastructure messages that pass * through MessageDispatcher; all others are immediately handed to the PH for * synchronous delivery). Like message consumption, the process of sending * messages depends on whether or not the ALQ is enabled as well as the type of * message being sent.<p> * * When the ALQ is enabled, SIF_Event messages are immediately stored in the * queue and will eventually be sent to the appropriate PH by a worker thread * that is built into the ALQ. This ensures that regardless of whether the * agent goes down or not, the events it has generated are guaranteed to make * their way to the ZIS eventually. SIF_Request and SIF_Response messages are * not handled in this way; rather, they are sent synchronously in the same * way as SIF_Events are sent when the ALQ is disabled.<p> * * When the ALQ is disabled, SIF_Event, SIF_Request, and SIF_Response messages * are immediately handed to the PH of each destination zone. If an exception * occurs before the message is acknowledged by the ZIS, it will propagate up * the call stack to the agent code that originally initiated the message (e.g. * to a Topic.publishEvent call). An agent can either retry the operation by * calling the same method a second time, or can abandon the transaction * altogether.<p> * * <b>Push vs. Pull Mode</b><p> * * Push and Pull mode have no effect on MessageDispatcher, its interfaces, or * its logic. When Push mode is active for a zone, the PH will receive incoming * messages as they're pushed by the ZIS. Those messages will then be handled * as described in "Message Consumption" above. When Pull mode is active for a * zone, the PH runs as a thread to periodically get messages from the ZIS. In * short, MessageDispatcher does not care how messages were obtained. It works * consistently in both modes.<p> * * <b>Selective Message Blocking</b><p> * * The ADK addresses Selective Message Blocking by forcing agents to use the * TrackQueryResults class to perform queries while processing SIF_Events. (If * an agent attempts to call a query method while a SIF_Event is being * dispatched for a given zone, an exception is thrown.) TrackQueryResults * houses all of the logic for Selective Message Blocking. When the ALQ is * enabled, TrackQueryResults does not invoke SMB; instead it draws upon the * ALQ for SIF_Responses. When the ALQ is disabled, TrackQueryResults keeps * a tab of which messages it has sent Intermediate acknowledgements for and * will eventually need to send Final acknowledgements for. To accomplish this * it works closely with MessageDispatcher so that when the dispatching of a * given SIF_Event has ended the TrackQueryResults object is asked to send its * pending Final acknowledgements. For more details on this consult that * class. * * * @author Eric Petersen * @version ADK 1.0 */ public class MessageDispatcher implements Runnable { protected Object fRunning; protected IAgentQueue fQueue; protected ZoneImpl fZone; protected String fSourceId; protected SIFParser fParser; protected boolean fKeepMsg; protected boolean fAckAckOnPull; protected Hashtable fEvDispCache; protected RequestCache fRequestCache; private final MessageIdCache msgIdCache = new MessageIdCache(); /** * Constructs a MessageDispatcher for a zone */ public MessageDispatcher( ZoneImpl zone ) throws ADKException { fRequestCache = RequestCache.getInstance( zone.getAgent() ); fZone = zone; fQueue = zone.fQueue; if( fQueue != null ) { if( !fQueue.isReady() ) throw new ADKQueueException("Agent Queue is not ready for agent \""+zone.getAgent().getId()+"\" zone \""+zone.getZoneId()+"\"",fZone); new Thread(this,zone.getAgent().getId()+"@"+zone.getZoneId()+".MessageDispatcher").start(); } fSourceId = zone.getAgent().getId(); fKeepMsg = zone.getAgent().getProperties().getKeepMessageContent(); fAckAckOnPull = zone.getAgent().getProperties().getPullAckAck(); try { fParser = SIFParser.newInstance(); } catch( ADKException adke ) { throw new InternalError(adke.toString()); } } /** * Find the QueryResults object for a zone by searching up the message * dispatching chain until a Zone, Topic, or Agent is found with a registered * QueryResults implementation. * * @param rsp The SIF_Response message (if a SIF_Response was received). * Either rsp or req must be specified, but not both. * @param req The SIF_Request message (if a SIF_Request is being sent). * Either rsp or req must be specified, but not both. * @param query Only applicable when <i>req</i> is non-null: The Query * associated with the SIF_Request * @param zone The Zone to begin the search at */ protected QueryResults getQueryResultsTarget( SIF_Response rsp, SIF_Request req, ElementDef objType, Query query, Zone zone ) throws SIFException { // // - First check TrackQueryResults for a matching pending request // - Next check the Topic, the Zone, and finally the Agent. The // message is dispatched to the first one that results a // QueryResults object // QueryResults target = null; SIFContext context = null; if( req != null ) { // First check TrackQueryResults TrackQueryResults tracker = (TrackQueryResults)TrackQueryResultsImpl.sRequestQueries.get(query); if( tracker != null ) { TrackQueryResultsImpl.sRequestQueries.remove(query); target = tracker; } else { SIF_Query q = req.getSIF_Query(); if( q == null ) throw new SIFException( SIFErrorCategory.XML_VALIDATION, SIFErrorCodes.XML_MISSING_MANDATORY_ELEMENT_6, "SIF_Request message missing mandatory element", "SIF_Query is required", fZone ); SIF_QueryObject qo = q.getSIF_QueryObject(); if( qo == null ) throw new SIFException( SIFErrorCategory.XML_VALIDATION, SIFErrorCodes.XML_MISSING_MANDATORY_ELEMENT_6, "SIF_Request message missing mandatory element", "SIF_QueryObject is required", fZone ); objType = ADK.DTD().lookupElementDef( qo.getObjectName() ); if( objType == null ) throw new SIFException( SIFErrorCategory.REQUEST_RESPONSE, SIFErrorCodes.REQRSP_INVALID_OBJ_3, "Agent does not support this object type", qo.getObjectName(), fZone ); } // Check to see if the Context is supported // TODO: Determine if a SIFException should be thrown at this point? try { context = req.getSIFContexts().get( 0 ); } catch( ADKNotSupportedException contextNotSupported ){ throw new SIFException( SIFErrorCategory.GENERIC, SIFErrorCodes.GENERIC_CONTEXT_NOT_SUPPORTED_4, contextNotSupported.getMessage(), fZone ); } } else if( rsp != null ) { // First check TrackQueryResults object to see if it is expecting // to be called for this SIF_Response String reqId = rsp.getSIF_RequestMsgId(); TrackQueryResults tracker = (TrackQueryResults)TrackQueryResultsImpl.sRequestMsgIds.get( reqId ); if( tracker != null ) { // Dispatch to the TrackQueryResults object target = tracker; } // Check to see if the Context is supported // TODO: Determine if a SIFException should be thrown at this point? try { context = rsp.getSIFContexts().get( 0 ); } catch( ADKNotSupportedException contextNotSupported ){ throw new SIFException( SIFErrorCategory.GENERIC, SIFErrorCodes.GENERIC_CONTEXT_NOT_SUPPORTED_4, contextNotSupported.getMessage(), fZone ); } } else throw new IllegalArgumentException("A SIF_Request or SIF_Response object must be passed to getQueryResultsTarget"); if( target == null ) { // Try the Topic... (if the context is default ) TopicImpl topic = (TopicImpl)fZone.getAgent().getTopicFactory().lookupInstance( objType, context ); if( topic != null ){ target = topic.fQueryResults; } if( target == null ) { // Next try the Zone... target = fZone.getQueryResults( context, objType); } if( target == null ) { // Finally, try the Agent... target = fZone.getAgent().getQueryResults( context, objType ); } } return target; } /** * Find the MessagingListenerImpl objects to notify when a message is * received or sent on this zone. */ protected static List<MessagingListener> getMessagingListeners( ZoneImpl zone ) { List<MessagingListener> v = new ArrayList<MessagingListener>(); // Contribute the Zone's listeners to the group v.addAll( zone.fMessagingListeners ); // Contribute the Agent's listeners to the group v.addAll( zone.getAgent().getMessagingListeners() ); return v; } /** * Dispatch a message.<p> * * @param msg The infrastructure message to dispatch */ public int dispatch( SIFMessagePayload msg ) throws SIFException, ADKMessagingException, ADKException, LifecycleException { String errTyp = null; List<MessagingListener> msgList = null; int status = 1; try { byte pload = ADK.DTD().getElementType(msg.getElementDef().name()); if( pload == SIFDTD.MSGTYP_SYSTEMCONTROL ) { SIFElement[] ch = ((SIF_SystemControl)msg).getSIF_SystemControlData().getChildren(); if( ch != null && ch.length > 0 ) { if( ch[0].getElementDef() == InfraDTD.SIF_SLEEP ) { fZone.execSleep(); } else if( ch[0].getElementDef() == InfraDTD.SIF_WAKEUP ) { fZone.execWakeup(); } else if( ch[0].getElementDef() == InfraDTD.SIF_PING ) { // Notify MessagingListeners... msgList = getMessagingListeners( fZone ); if( msgList != null && msgList.size() > 0 ) { SIFMessageInfo msginfo = new SIFMessageInfo( msg,fZone ); for( Iterator vi = msgList.iterator(); vi.hasNext(); ) { ((MessagingListener)vi.next()).onMessageProcessed( SIFMessagingListener.SIF_SYSTEM_CONTROL, msginfo ); } } if( fZone.isSleeping( ADKFlags.QUEUE_LOCAL ) ) return 8; return 1; } else if( ch[0].getElementDef() == InfraDTD.SIF_CANCELREQUESTS ) { // Remove requests from cache - JEN SIF_CancelRequests cancelRequests = (SIF_CancelRequests)ch[0]; for (SIF_RequestMsgId sif_RequestMsgId : cancelRequests.getSIF_RequestMsgIds()) { RequestInfo requestInfo = fRequestCache.getRequestInfo(sif_RequestMsgId.getValue(), null); if (requestInfo == null) fZone.log.warn( "Agent can not cancel request " + sif_RequestMsgId.getValue() ); } return 1; } else { fZone.log.warn( "Received unknown SIF_SystemControlData: " + ch[0].tag() ); throw new SIFException( SIFErrorCategory.XML_VALIDATION, SIFErrorCodes.XML_MISSING_MANDATORY_ELEMENT_6, "SIF_SystemControlData must contain SIF_Ping, SIF_Wakeup, or SIF_Sleep", fZone ); } // Notify MessagingListeners... msgList = getMessagingListeners( fZone ); if( msgList != null && msgList.size() > 0 ) { SIFMessageInfo msginfo = new SIFMessageInfo(msg,fZone); for( Iterator vi = msgList.iterator(); vi.hasNext(); ) { ((MessagingListener)vi.next()).onMessageProcessed( SIFMessagingListener.SIF_SYSTEM_CONTROL, msginfo ); } } } return status; } // If zone is asleep, return status code if( fZone.isSleeping( ADKFlags.QUEUE_LOCAL ) ) return SIFStatusCodes.SLEEPING_8; // do not allow duplicate message into dispatch logic String msgId = msg.getMsgId(); if (msgIdCache.containsKey(msgId)) { return SIFStatusCodes.DUPLICATE_MESSAGE_7; } else { msgIdCache.put(msgId, msgId); } // Some agents don't want to receive messages - for example, the // SIFSend ADK Example agent. This is very rare but we offer a property // to allow for it if( fZone.getProperties().getDisableMessageDispatcher() ) return status; else if (msg instanceof SIF_ServiceInput) { dispatchServiceRequest((SIF_ServiceInput)msg); } else if (msg instanceof SIF_ServiceOutput) { dispatchServiceResponse((SIF_ServiceOutput)msg); } else if (msg instanceof SIF_ServiceNotify) { dispatchServiceNotify((SIF_ServiceNotify)msg); } else { switch( pload ) { case SIFDTD.MSGTYP_EVENT: errTyp = "Subscriber.onEvent"; status = dispatchEvent( (SIF_Event)msg ); break; case SIFDTD.MSGTYP_REQUEST: errTyp = "Publisher.onRequest"; dispatchRequest( (SIF_Request)msg ); break; case SIFDTD.MSGTYP_RESPONSE: errTyp = "QueryResults"; dispatchResponse( (SIF_Response)msg ); break; default: fZone.log.warn( "Agent does not know how to dispatch " + msg.getElementDef().name() + " messages" ); throw new SIFException( SIFErrorCategory.GENERIC, SIFErrorCodes.GENERIC_MESSAGE_NOT_SUPPORTED_2, "Message not supported", msg.getElementDef().name(), fZone ); } } } catch( LifecycleException le ) { throw le; } catch( SIFException se ) { msgIdCache.remove(msg.getMsgId()); // Check if ADKException.setRetry() was called; use transport // error category to force ZIS to resend message if( se.getRetry() ) { se.setErrorCategory( SIFErrorCategory.TRANSPORT ); se.setErrorCode( SIFErrorCodes.WIRE_GENERIC_ERROR_1 ); } logAndRethrow("SIFException in " + errTyp + " message handler for " + msg.getElementDef().name(), se); } catch( ADKZoneNotConnectedException adkznce ){ msgIdCache.remove(msg.getMsgId()); // Received a message while the zone was disconnected. Return a system transport // error so that the message is not removed from the queue SIFException sifEx = new SIFException( SIFErrorCategory.TRANSPORT, SIFErrorCodes.WIRE_GENERIC_ERROR_1, adkznce.getMessage(), fZone ); logAndRethrow( "Message received while zone is not connected", sifEx ); } catch( ADKException adke ) { msgIdCache.remove(msg.getMsgId()); // Check if ADKException.setRetry() was called; use transport // error category to force ZIS to resent message if( adke.getRetry() ) { logAndThrowRetry(adke.getMessage(), adke); } logAndThrowSIFException("ADKException in " + errTyp + " message handler for " + msg.getElementDef().name(), adke); } catch( Throwable uncaught ) { msgIdCache.remove(msg.getMsgId()); logAndThrowSIFException("Uncaught exception in " + errTyp + " message handler for " + msg.getElementDef().name(), uncaught); } return status; } /* * */ private void dispatchServiceNotify(SIF_ServiceNotify msg) throws ADKException { // TODO Complete method stub ADK.getLog().info("Dispatch Service Notify called with " + msg.toString()); ArrayList<SIFZoneServiceProxy> targets = fZone.getServiceNotifiers(msg.getSIF_Service()); if (targets != null) { SIF_Body sifBody = msg.getSIF_Body(); //SIFDataObject[] dataObjects = new SIFDataObject[rsp.getChildCount()]; ArrayList<SIFDataObject> list = new ArrayList<SIFDataObject>(); for (SIFElement element : sifBody.getChildList()) { if (element instanceof SIFDataObject) list.add((SIFDataObject) element); } //SIFPullParser parser = new SIFPullParser(); //SIFElement sifElement = parser.parse(xmlData); DataObjectInputStreamImpl data = DataObjectInputStreamImpl.newInstance(); SIFDataObject[] wua = new SIFDataObject[list.size()]; for (int i = 0; i < list.size(); ++i) wua[i] = list.get(i); data.setData(wua); MessageInfo info = new SIFMessageInfo(msg,fZone); for (SIFZoneServiceProxy target : targets) target.onServiceEvent(data, msg.getSIF_Error(), fZone, info); } } /** * Dispatch a SIF_Event.<p> * * <b>When ALQ Disabled:</b> Dispatching of this event is handled in a * separate EvDisp thread in case SMB is invoked. This makes it possible * to asynchronously return a SIF_Ack code to the dispatchEvent() caller * before the handling of the event by the Subscriber is completed. The * EvDisp also tracks the internal dispatch state of this particular message. * The Topic matching the object type is then notified via its Subscriber's * onEvent method. If a TrackQueryResults object is created within that * method, its constructor will wakeup the EvDisp thread, instructing it to * return a value of 2 (Intermediate). If no TrackQueryResults object is * instantiated during the Subscriber.onEvent method, the EvDisp thread * returns a 1 (Immediate) status code upon completion.<p> * * <b>When ALQ Enabled:</b> Dispatching is immediate. The Topic matching * the object type is then notified via its Subscriber's onEvent method, * then processing ends. No EvDisp thread is needed because if a * TrackQueryResults is used it will draw upon the ALQ instead of invoking * SMB on the zone server.<p> * * Note if an exception is thrown at any time during the processing of a * SIF_Event, it is propagated up the call stack. The PH must not return a * SIF_Ack for the message; the ALQ must not delete the message from its * queue.<p> * */ protected int dispatchEvent( SIF_Event sifEvent ) throws ADKException { if( ( ADK.debug & ADK.DBG_MESSAGING_EVENT_DISPATCHING ) != 0 ) fZone.log.debug( "Dispatching SIF_Event ("+sifEvent.getMsgId()+")..." ); // Was this event reported by this agent? if( !fZone.getProperties().getProcessEventsFromSelf() && sifEvent.getHeader().getSIF_SourceId().equals( fZone.getAgent().getId() ) ) { if( ( ADK.debug & ADK.DBG_MESSAGING_EVENT_DISPATCHING ) != 0 ) fZone.log.debug( "SIF_Event ignored because it was originally reported by this agent (see the adk.messaging.processEventsFromSelf property)" ); return 1; } SIF_ObjectData odata = sifEvent.getSIF_ObjectData(); if( odata == null ) throw new SIFException( SIFErrorCategory.XML_VALIDATION, SIFErrorCodes.XML_MISSING_MANDATORY_ELEMENT_6, "SIF_Event message missing mandatory element", "SIF_ObjectData is a required element", fZone ); // // Loop through all SIF_EventObjects inside this SIF_Event and dispatch // to corresponding topics // SIF_EventObject eventObj = odata.getSIF_EventObject(); if( eventObj == null ) throw new SIFException( SIFErrorCategory.XML_VALIDATION, SIFErrorCodes.XML_MISSING_MANDATORY_ELEMENT_6, "SIF_Event message missing mandatory element", "SIF_ObjectData/SIF_EventObject is a required element", fZone ); int ackCode=1; int thisCode=1; SIFMessageInfo msgInfo = new SIFMessageInfo( sifEvent, fZone ); ElementDef typ = ADK.DTD().lookupElementDef( eventObj.getObjectName() ); if( typ == null ) { // SIF Data Object type not supported throw new SIFException( SIFErrorCategory.EVENTS, SIFErrorCodes.EVENT_INVALID_EVENT_3, "Agent does not support this object type", eventObj.getObjectName(), fZone ); } // TODO: For now, the ADK only routes SIF Events to the first context // in the event. This needs to be implemented to support // events in multiple contexts SIFContext eventContext = msgInfo.getSIFContexts()[0]; Subscriber target = null; Topic topic = null; // // Lookup the Topic for this SIF object type // Topics are only used for the SIF Default context // topic = fZone.getAgent().getTopicFactory().lookupInstance( typ, eventContext ); if( topic != null ){ target = topic.getSubscriber(); } if( target == null ) { // Is a Subscriber registered with the Zone? target = fZone.getSubscriber( eventContext, typ ); if( target == null ) { // Is a Subscriber registered with the Agent? target = fZone.getAgent().getSubscriber( eventContext, typ ); if( target == null ) { // // No Subscriber message handler found. Try calling the Undeliverable- // MessageHandler for the zone or agent. If none is registered, // return an error SIF_Ack indicating the object type is not // supported. // boolean handled = false; UndeliverableMessageHandler errHandler = fZone.getErrorHandler(); if( errHandler != null ) { handled = errHandler.onDispatchError( sifEvent, fZone, msgInfo ); // Notify MessagingListeners... List<MessagingListener> mList = getMessagingListeners( fZone ); for( MessagingListener ml : mList ){ ml.onMessageProcessed( SIFMessagingListener.SIF_EVENT, msgInfo ); } } if( !handled ) { fZone.log.warn( "Received a SIF_Event (" + sifEvent.getMsgId() + "), but no Subscriber object is registered to handle it" ); throw new SIFException( SIFErrorCategory.EVENTS, SIFErrorCodes.EVENT_INVALID_EVENT_3, "Agent does not support this object type", eventObj.getObjectName(), fZone ); } return 1; } } } // // Call Subscriber.onEvent with the event data // SIFElement[] arr = eventObj.getChildren(); SIFDataObject[] data = new SIFDataObject[arr.length]; for( int x = 0; x < arr.length; x++ ){ data[x] = (SIFDataObject)arr[x]; } // Wrap in an Event object DataObjectInputStreamImpl dataStr = DataObjectInputStreamImpl.newInstance(); dataStr.setData( data ); Event adkEvent = new Event(dataStr, eventObj.getAction(), data[0].getElementDef()); adkEvent.setZone(fZone); adkEvent.setContexts( new SIFContext[] { eventContext} ); if( ( ADK.debug & ADK.DBG_MESSAGING_EVENT_DISPATCHING ) != 0 ) fZone.log.debug( "SIF_Event contains " + data.length + " " + eventObj.getObjectName() + " objects (" + eventObj.getAction() + ")" ); if( fQueue == null ) { if( ( ADK.debug & ADK.DBG_MESSAGING_EVENT_DISPATCHING ) != 0 ) fZone.log.debug( "Dispatching SIF_Event to Subscriber message handler via EvDisp" ); // // -- No ALQ available -- // Dispatch in a separate EvDisp thread. Block until an ack // status code is available, then return it // EvDisp disp = null; try { disp = checkoutEvDisp(adkEvent); disp.dispatch(target,adkEvent,fZone,topic,msgInfo); thisCode = disp.waitForAckCode(); } finally { checkinEvDisp(adkEvent); } } else { if( ( ADK.debug & ADK.DBG_MESSAGING_EVENT_DISPATCHING ) != 0 ) fZone.log.debug( "Dispatching SIF_Event to Subscriber message handler" ); // // -- ALQ is available -- // Dispatch immediately. // try { target.onEvent(adkEvent,fZone,msgInfo); } catch( SIFException sifEx ) { throw sifEx; } catch( Throwable thr ) { throw new SIFException( SIFErrorCategory.EVENTS, SIFErrorCodes.EVENT_GENERIC_ERROR_1, "Error processing SIF_Event", "Exception in Subscriber.onEvent message handler: " + ADKStringUtils.getStackTrace(thr), fZone ); } thisCode = 1; } if( thisCode > ackCode ) ackCode = thisCode; if( ( ADK.debug & ADK.DBG_MESSAGING ) != 0 ) fZone.log.debug( "SIF_Event (" + sifEvent.getMsgId() + ") dispatching returning SIF_Ack status "+ackCode ); return ackCode; } /** * Dispatch a SIF_Response.<p> * * SIF_Response messages are dispatched as follows: * * <ul> * <li> * If a TrackQueryResults object issued the original SIF_Request * during this agent session (i.e. the agent process has not * terminated since the SIF_Request was issued), the response is * dispatched to that TrackQueryResults object via its QueryResults * interface. * </li> * <li> * If a Topic exists for the data type associated with the * SIF_Response, it is dispatched to the QueryResults object * registered with that Topic. * </li> * <li> * If no Topic exists for the data type associated with the * SIF_Response, it is dispatched to the QueryResults object * registered with the Zone from which the SIF_Response was * received. * </li> * </ul> * * <b>SIF_ZoneStatus</b> is handled specially. When Zone.awaitingZoneStatus * returns true, the agent is blocking on a call to Zone.getZoneStatus(). * In this case, the SIF_ZoneStatus object is routed directly to the Zone * object instead of being dispatched via the usual QueryResults mechanism. * <p> */ protected void dispatchResponse( SIF_Response rsp ) throws ADKException { // block thread until Zone.query() has completed in case it is in the // midst of a SIF_Request. This is done to ensure that we don't receive // the SIF_Response from the zone before the ADK and agent have finished // with the SIF_Request in Zone.query() fZone.waitForRequestsToComplete(); boolean retry = false; RequestInfo reqInfo = null; ADKException cacheErr = null; try { try { reqInfo = fRequestCache.lookupRequestInfo( rsp.getSIF_RequestMsgId(), fZone ); } catch( ADKException adke ) { cacheErr = adke; } // if( BuildOptions.PROFILED ) { // if( reqInfo != null ) { // ProfilerUtils.profileStart( String.valueOf( openadk.profiler.api.OIDs.ADK_SIFRESPONSE_REQUESTOR_MESSAGING ), ADK.DTD().lookupElementDef( reqInfo.getObjectType() ), rsp.getMsgId() ); // } // } SIF_ObjectData od = rsp.getSIF_ObjectData(); List<SIFElement> elementList = null; SIFElement firstChild = null; if( od != null ){ elementList = od.getChildList(); if( elementList.size() > 0 ){ firstChild = elementList.get( 0 ); } } SIF_Error error = rsp.getSIF_Error(); SIF_ExtendedQueryResults seqResults = rsp.getSIF_ExtendedQueryResults(); // Validate that the SIF_Response has at least one of the required choice // elements, SIF_Error, SIF_ObjectData, or SIF_ExtendedQueryResults if( od == null && error == null && seqResults == null ) { throw new SIFException( SIFErrorCategory.XML_VALIDATION, SIFErrorCodes.XML_MISSING_MANDATORY_ELEMENT_6, "SIF_Response missing mandatory element", "SIF_ObjectData is a required element of SIF_Response", fZone ); } // TODO: For now, the ADK does not support SIF_ExtendedQueryResults if( seqResults != null ){ throw new SIFException( SIFErrorCategory.REQUEST_RESPONSE, SIFErrorCodes.REQRSP_NO_SUPPORT_FOR_SIF_EXT_QUERY, "Agent does not support SIF_ExtendedQueryResults", fZone ); } String objectType = reqInfo != null ? reqInfo.getObjectType() : null; if( objectType == null && firstChild != null ){ objectType = firstChild.getElementDef().tag( rsp.getSIFVersion() ); } if( objectType != null && objectType.equalsIgnoreCase("SIF_ZoneStatus") ) { // SIF_ZoneStatus is a special case if( fZone.awaitingZoneStatus() ) { fZone.setZoneStatus((SIF_ZoneStatus)firstChild ); return; } } // // If the SIF_Response has no SIF_ObjectData elements but does // have a SIF_Error child, the associated object type can // only be gotten from the RequestCache, but that had // failed so try and call the UndeliverableMessageHandler. // if( reqInfo == null && objectType == null ) { boolean handled = false; UndeliverableMessageHandler errHandler = fZone.getErrorHandler(); if( errHandler != null ) { SIFMessageInfo msginfo = new SIFMessageInfo(rsp,fZone); handled = errHandler.onDispatchError( rsp, fZone, msginfo ); // Notify MessagingListeners... for( MessagingListener ml : getMessagingListeners( fZone ) ){ ml.onMessageProcessed( SIFMessagingListener.SIF_RESPONSE, msginfo ); } } if( !handled ) fZone.log.warn( "Received a SIF_Response message with MsgId " + rsp.getMsgId() + " (for SIF_Request with MsgId " + rsp.getSIF_RequestMsgId() + ") " + " containing an empty result set or a SIF_Error, but failed to obtain the SIF Data Object" + " type from the RequestCache due to an error.", cacheErr ); return; } if( reqInfo == null ) { reqInfo = new UnknownRequestInfo( rsp.getSIF_RequestMsgId(), objectType ); } // Decide where to send this response QueryResults target = getQueryResultsTarget(rsp,null,ADK.DTD().lookupElementDef( objectType ), null, fZone ); if( target == null ) { boolean handled = false; UndeliverableMessageHandler errHandler = fZone.getErrorHandler(); if( errHandler != null ) { SIFMessageInfo msginfo = new SIFMessageInfo(rsp,fZone); msginfo.setSIFRequestInfo( reqInfo ); handled = errHandler.onDispatchError( rsp, fZone, msginfo ); // Notify MessagingListeners... for( MessagingListener ml : getMessagingListeners( fZone ) ){ ml.onMessageProcessed( SIFMessagingListener.SIF_RESPONSE, msginfo ); } } if( !handled ) fZone.log.warn( "Received a SIF_Response message with MsgId " + rsp.getMsgId() + " (for SIF_Request with MsgId " + rsp.getSIF_RequestMsgId() + "), but no QueryResults object is registered to handle it or the request was issued by a TrackQueryResults that has timed out" ); return; } // // Dispatch the message... // ElementDef sifRequestObjectDef = ADK.DTD().lookupElementDef( objectType ); DataObjectInputStreamImpl dataStr = DataObjectInputStreamImpl.newInstance(); dataStr.fObjType = sifRequestObjectDef; if( error == null && elementList != null ) { // Convert to a SIFDataObject array SIFDataObject[] data = new SIFDataObject[ elementList.size() ]; elementList.toArray( data ); // Let the QueryResults object process the message dataStr.setData( data ); } SIFMessageInfo msgInf = new SIFMessageInfo(rsp,fZone); msgInf.setSIFRequestInfo( reqInfo ); msgInf.setSIFRequestObjectType( sifRequestObjectDef ); target.onQueryResults(dataStr,error,fZone,msgInf); // Notify MessagingListeners... for( MessagingListener ml : getMessagingListeners( fZone ) ){ ml.onMessageProcessed( SIFMessagingListener.SIF_RESPONSE, msgInf ); } } catch( ADKException adkEx ) { retry = adkEx.getRetry(); throw adkEx; } finally { // If the reqInfo variable came from the cache, and retry is set to false, // remove it from the cache if this is the last packet if( !( reqInfo instanceof UnknownRequestInfo ) && !retry ) { String morePackets = rsp.getSIF_MorePackets(); if( !( morePackets != null && morePackets.equalsIgnoreCase( "yes") ) ) { // remove from the cache fRequestCache.getRequestInfo( rsp.getSIF_RequestMsgId(), fZone ); } } if( BuildOptions.PROFILED ) { if( reqInfo != null ) { ProfilerUtils.profileStop(); } } } } protected void dispatchServiceResponse( SIF_ServiceOutput rsp ) throws ADKException { SIFZoneServiceProxy target = fZone.getServiceSubscriber(rsp.getSIF_Service()); if (target != null) { // String xmlData = rsp.getSIF_Body(); //SIFDataObject[] dataObjects = new SIFDataObject[rsp.getChildCount()]; ArrayList<SIFDataObject> doList = new ArrayList<SIFDataObject>(); for (SIFElement element : rsp.getChildList()) { if (element instanceof SIFDataObject) { SIFDataObject addThis = (SIFDataObject) element; doList.add(addThis); } } //SIFPullParser parser = new SIFPullParser(); //SIFElement sifElement = parser.parse(xmlData); DataObjectInputStreamImpl data = DataObjectInputStreamImpl.newInstance(); SIFDataObject[] wua = new SIFDataObject[doList.size()]; for (int i = 0; i < doList.size(); ++i) wua[i] = doList.get(i); data.setData(wua); MessageInfo info = new SIFMessageInfo(rsp,fZone); target.onQueryResults(data, rsp.getSIF_Error(), fZone, info); } } /* * Service */ protected void dispatchServiceRequest( SIF_ServiceInput req ) throws ADKException { SIFVersion renderAsVer = null; ElementDef typ = req.getElementDef(); int maxBufSize = 0; boolean rethrow = false; try { // block thread until Zone.query() has completed in case it is in the // midst of a SIF_Request and the destination of that request is this // agent (i.e. a request of self). This is done to ensure that we don't // receive the SIF_Request from the zone before the ADK and agent have // finished issuing it in Zone.query() fZone.waitForRequestsToComplete(); // // Check SIF_Version. If the version is not supported by the ADK, // fail the SIF_Request with an error SIF_Ack. If the version is // supported, continue on; the agent may not support this version, // but that will be determined later and will result in a SIF_Response // with a SIF_Error payload. // /* SIF_Version[] versions = req.getSIF_Versions(); if( versions == null || versions.length == 0 ) { rethrow = true; throw new SIFException( SIFErrorCategory.XML_VALIDATION, SIFErrorCodes.XML_MISSING_MANDATORY_ELEMENT_6, "SIF_Request/SIF_Version is a mandatory element", fZone ); } */ // SIF_Version specifies the version of SIF that will be used to render // the SIF_Responses // TODO: SIF now allows multiple versions within a SIF_Request // The ADK needs to keep the list and write the response back in the // latest supported version /* renderAsVer = SIFVersion.parse( versions[0].getTextValue() ); if( !ADK.isSIFVersionSupported( renderAsVer ) ) { rethrow = true; throw new SIFException( SIFErrorCategory.REQUEST_RESPONSE, SIFErrorCodes.REQRSP_UNSUPPORTED_SIFVERSION_7, "SIF_Version " + renderAsVer + " is not supported by this agent", fZone ); } */ // Check max buffer size Integer bufferSize = req.getSIF_MaxBufferSize(); if( bufferSize == null ) { rethrow = true; throw new SIFException( SIFErrorCategory.XML_VALIDATION, SIFErrorCodes.XML_MISSING_MANDATORY_ELEMENT_6, "SIF_Request/SIF_MaxBufferSize is a mandatory element", fZone ); } maxBufSize = bufferSize; if( maxBufSize < 4096 || maxBufSize > Integer.MAX_VALUE ) { throw new SIFException( SIFErrorCategory.REQUEST_RESPONSE, SIFErrorCodes.REQRSP_UNSUPPORTED_MAXBUFFERSIZE_8, "Invalid SIF_MaxBufferSize value (" + maxBufSize + ")", "Acceptable range is 4096 to " + Integer.MAX_VALUE, fZone ); } // Check to see if the Context is supported try { req.getSIFContexts(); } catch( ADKNotSupportedException contextNotSupported ){ throw new SIFException( SIFErrorCategory.GENERIC, SIFErrorCodes.GENERIC_CONTEXT_NOT_SUPPORTED_4, contextNotSupported.getMessage(), fZone ); } } catch( SIFException se ) { if( BuildOptions.PROFILED ){ ProfilerUtils.profileStop(); } if( !rethrow ) { // sendErrorResponse( req, se, renderAsVer, maxBufSize ); } // rethrow all errors at this point throw se; } // For now, SIFContext is not repeatable in SIF Requests SIFContext requestContext = req.getSIFContexts().get( 0 ); Object target = null; if( target == null ) { target = fZone.getServicePublisher(req.getSIF_Service()); if( target == null ) { UndeliverableMessageHandler errHandler = fZone.getErrorHandler(); boolean handled = false; if( errHandler != null ) { SIFMessageInfo msginfo = new SIFMessageInfo( req, fZone ); handled = errHandler.onDispatchError( req, fZone, msginfo ); // Notify MessagingListeners... for( MessagingListener ml : getMessagingListeners( fZone ) ){ ml.onMessageProcessed( SIFMessagingListener.SIF_REQUEST, msginfo ); } } if( !handled ) { SIFException sifEx = new SIFException( SIFErrorCategory.REQUEST_RESPONSE, SIFErrorCodes.REQRSP_INVALID_OBJ_3, "Agent does not support this object type", "SIF_ServiceInput", fZone ); // sendErrorResponse( req,sifEx, renderAsVer, maxBufSize ); throw sifEx; } else { if( BuildOptions.PROFILED ) ProfilerUtils.profileStop(); return; } } } boolean success = false; // DataObjectOutputStreamImpl out = null; JEN BaseObjectOutputStream out = null; SIFMessageInfo msgInfo = new SIFMessageInfo( req, fZone ); try { // Create a stream the Publisher can write results to out = ServiceOutputStreamImpl.newInstance(); ((SIFZoneService)target).onRequest( (ServiceOutputFileStream)out,req,fZone,msgInfo ); // Notify MessagingListeners... for( MessagingListener ml : getMessagingListeners( fZone ) ){ ml.onMessageProcessed( SIFMessagingListener.SIF_REQUEST, msgInfo ); } } catch( SIFException se ) { // For a SIF_Request, a SIFException (other than a Transport Error) // does not mean to return an error ack but instead to return a // valid SIF_Response with a SIF_Error payload (see the SIF // Specification). Transport Errors must be returned to the ZIS so // that the message will be retried later. // if( se.getRetry() || se.getSIFErrorCategory() == SIFErrorCategory.TRANSPORT ) { success = false; //retry was requested, so we have to tell the output stream to not send an empty response out.deferResponse(); fZone.log.warn( "SIFException in " + "Publisher.onRequest" + ": Retry was requested, so deferring response because of this error", se ); throw se; } fZone.log.warn( "SIFException in " + ("Publisher.onRequest"), se ); out.setError( se.getError() ); } catch( ADKException adke ) { // If retry requested, throw a Transport Error back to the ZIS // instead of returning a SIF_Error in the SIF_Response payload if( adke.getRetry() ) { success = false; // retry was requested, so we have to tell the output stream to not send an empty response out.deferResponse(); fZone.log.warn( "ADKException in " + "Publisher.onRequest" + ": Retry was requested, so deferring response because of this error", adke ); throw adke; } fZone.log.error( "Exception in " + "Publisher.onRequest", adke ); // Return SIF_Error payload in SIF_Response SIF_Error err = new SIF_Error(); err.setSIF_Category( SIFErrorCategory.GENERIC ); err.setSIF_Code( SIFErrorCodes.GENERIC_GENERIC_ERROR_1 ); // if( BuildOptions.PROFILED ) // ProfilerUtils.profileStart( String.valueOf( openadk.profiler.api.OIDs.ADK_SIFREQUEST_RESPONDER_MESSAGING ), typ, req.getMsgId() ); err.setSIF_Desc( adke.getMessage() ); err.setSIF_ExtendedDesc( ADKStringUtils.getStackTrace(adke) ); out.setError( err ); } catch( Throwable thr ) { fZone.log.error( "Exception in " + "Publisher.onRequest", thr ); SIF_Error err = new SIF_Error(); err.setSIF_Category( SIFErrorCategory.GENERIC ); err.setSIF_Code( SIFErrorCodes.GENERIC_GENERIC_ERROR_1 ); err.setSIF_Desc( "Agent could not process the SIF_Request at this time" ); err.setSIF_ExtendedDesc( ADKStringUtils.getStackTrace(thr) ); out.setError( err ); } finally { try { out.close(); } catch( Exception ignored ) { fZone.log.warn( "Ignoring exception in out.close()", ignored ); } try { out.commit(); } catch( Exception ignored ) { fZone.log.warn( "Ignoring exception in out.commit()", ignored ); } if( BuildOptions.PROFILED ) ProfilerUtils.profileStop(); } } /** * Dispatch a SIF_Request.<p> * * <b>When ALQ Disabled:</b> The SIF_Request is immediately dispatched to * the Publisher of the associated topic. Only after the Publisher has * returned a result does this method return, causing the SIF_Request to * be acknowledged. The result data returned by the Publisher is handed to * the zone's ResponseDelivery thread, which sends SIF_Response messages to * the ZIS until all of the result data has been sent, potentially with * multiple SIF_Response packets. Note without the ALQ, there is the * potential for the agent to terminate before all data has been sent, * causing some results to be lost. In this case the SIF_Request will have * never been ack'd and will be processed again the next time the agent * is started. * <p> * * <b>When ALQ Enabled:</b> The SIF_Request is placed in the ALQ where it * will be consumed by the zone's ResponseDelivery thread at a later time. * This method returns immediately, causing the SIF_Request to be * acknowledged. The ResponseDelivery handles dispatching the request to * the Publisher of the associated topic, and also handles returning * SIF_Response packets to the ZIS. With the ALQ, the processing of the * SIF_Request and the returning of all SIF_Response data is guaranteed * because the original SIF_Request will not be removed from the ALQ until * both of these activities have completed successfully (even over multiple * agent sessions). * <p> * * Note that any error that occurs during a SIF_Request should result in a * successful SIF_Ack (because the SIF_Request was received successfully), * and a single SIF_Response with a SIF_Error payload. The SIF Compliance * harness checks for this. * <p> * * @param req The SIF_Request to process */ protected void dispatchRequest( SIF_Request req ) throws ADKException { SIFVersion renderAsVer = null; SIF_Query q = null; SIF_QueryObject qo = null; ElementDef typ = null; int maxBufSize = 0; boolean rethrow = false; boolean isReportObject = false; try { // block thread until Zone.query() has completed in case it is in the // midst of a SIF_Request and the destination of that request is this // agent (i.e. a request of self). This is done to ensure that we don't // receive the SIF_Request from the zone before the ADK and agent have // finished issuing it in Zone.query() fZone.waitForRequestsToComplete(); // // Check SIF_Version. If the version is not supported by the ADK, // fail the SIF_Request with an error SIF_Ack. If the version is // supported, continue on; the agent may not support this version, // but that will be determined later and will result in a SIF_Response // with a SIF_Error payload. // SIF_Version[] versions = req.getSIF_Versions(); if( versions == null || versions.length == 0 ) { rethrow = true; throw new SIFException( SIFErrorCategory.XML_VALIDATION, SIFErrorCodes.XML_MISSING_MANDATORY_ELEMENT_6, "SIF_Request/SIF_Version is a mandatory element", fZone ); } // SIF_Version specifies the version of SIF that will be used to render // the SIF_Responses // The ADK needs to keep the list and write the response back in the // latest supported version SIFVersion[] candidateVersions = new SIFVersion[versions.length]; int i = 0; for (SIF_Version version : versions) { candidateVersions[i++] = SIFVersion.parse(version.getTextValue()); } renderAsVer = ADK.getLatestSupportedVersion(candidateVersions); if( !ADK.isSIFVersionSupported( renderAsVer ) ) { rethrow = true; throw new SIFException( SIFErrorCategory.REQUEST_RESPONSE, SIFErrorCodes.REQRSP_UNSUPPORTED_SIFVERSION_7, "SIF_Version " + renderAsVer + " is not supported by this agent", fZone ); } // Check max buffer size Integer bufferSize = req.getSIF_MaxBufferSize(); if( bufferSize == null ) { rethrow = true; throw new SIFException( SIFErrorCategory.XML_VALIDATION, SIFErrorCodes.XML_MISSING_MANDATORY_ELEMENT_6, "SIF_Request/SIF_MaxBufferSize is a mandatory element", fZone ); } maxBufSize = bufferSize; if( maxBufSize < 4096 || maxBufSize > Integer.MAX_VALUE ) { throw new SIFException( SIFErrorCategory.REQUEST_RESPONSE, SIFErrorCodes.REQRSP_UNSUPPORTED_MAXBUFFERSIZE_8, "Invalid SIF_MaxBufferSize value (" + maxBufSize + ")", "Acceptable range is 4096 to " + Integer.MAX_VALUE, fZone ); } // Check to see if the Context is supported try { req.getSIFContexts(); } catch( ADKNotSupportedException contextNotSupported ){ throw new SIFException( SIFErrorCategory.GENERIC, SIFErrorCodes.GENERIC_CONTEXT_NOT_SUPPORTED_4, contextNotSupported.getMessage(), fZone ); } // Lookup the SIF_QueryObject q = req.getSIF_Query(); if( q == null ) { // If it's a SIF_ExtendedQuery or SIF_Example, throw the appropriate error if( req.getSIF_ExtendedQuery() != null ){ throw new SIFException( SIFErrorCategory.REQUEST_RESPONSE, SIFErrorCodes.REQRSP_NO_SUPPORT_FOR_SIF_EXT_QUERY, "SIF_ExtendedQuery is not supported", fZone ); } else { throw new SIFException( SIFErrorCategory.XML_VALIDATION, SIFErrorCodes.XML_MISSING_MANDATORY_ELEMENT_6, "SIF_Request/SIF_Query is a mandatory element", fZone ); } } qo = q.getSIF_QueryObject(); if( qo == null ) { rethrow = true; throw new SIFException( SIFErrorCategory.XML_VALIDATION, SIFErrorCodes.XML_MISSING_MANDATORY_ELEMENT_6, "SIF_Query/SIF_QueryObject is a mandatory element", fZone ); } // Lookup the ElementDef for the requested object type typ = ADK.DTD().lookupElementDef( qo.getObjectName() ); if( typ == null ) { throw new SIFException( SIFErrorCategory.REQUEST_RESPONSE, SIFErrorCodes.REQRSP_INVALID_OBJ_3, "Agent does not support this object type: " + qo.getObjectName(), fZone ); } // if( BuildOptions.PROFILED ) // ProfilerUtils.profileStart( String.valueOf( openadk.profiler.api.OIDs.ADK_SIFREQUEST_RESPONDER_MESSAGING ), typ, req.getMsgId() ); isReportObject = ( ReportingDTD.SIF_REPORTOBJECT != null && typ == ReportingDTD.SIF_REPORTOBJECT ); } catch( SIFException se ) { if( BuildOptions.PROFILED ){ ProfilerUtils.profileStop(); } if( !rethrow ){ sendErrorResponse( req, se, renderAsVer, maxBufSize ); } // rethrow all errors at this point throw se; // // Capture the SIFException so it can be written to the output stream // // and thus returned as the payload of the SIF_Response message later // // in this function. // error = se; // fZone.log.error( "Error in dispatchRequest that will be put into the SIF_Response", se ); } // For now, SIFContext is not repeatable in SIF Requests SIFContext requestContext = req.getSIFContexts().get( 0 ); Object target = null; // // Lookup the Publisher for this object type using Topics, // but only if the context is the Default context // if( typ != null && SIFContext.DEFAULT.equals( requestContext ) ){ Topic topic = null; topic = fZone.getAgent().getTopicFactory().lookupInstance( typ, requestContext ); if( topic != null ) { if( isReportObject ) target = topic.getReportPublisher(); else target = topic.getPublisher(); } } if( target == null ) { // Next check the Zone if( isReportObject ) target = fZone.getReportPublisher( requestContext ); else target = fZone.getPublisher( requestContext, typ); if( target == null ) { // Finally, check the Agent if( isReportObject ) target = fZone.getAgent().getReportPublisher( requestContext ); else target = fZone.getAgent().getPublisher( requestContext, typ ); if( target == null ) { // // No Publisher message handler found. Try calling the Undeliverable- // MessageHandler for the zone or agent. If none is registered, // return an error SIF_Ack indicating the object type is not // supported. // boolean handled = false; UndeliverableMessageHandler errHandler = fZone.getErrorHandler(); if( errHandler != null ) { SIFMessageInfo msginfo = new SIFMessageInfo( req, fZone ); handled = errHandler.onDispatchError( req, fZone, msginfo ); // Notify MessagingListeners... for( MessagingListener ml : getMessagingListeners( fZone ) ){ ml.onMessageProcessed( SIFMessagingListener.SIF_REQUEST, msginfo ); } } if( !handled ) { if( isReportObject ) fZone.log.warn( "Received a SIF_Request for " + qo.getObjectName() + " (MsgId=" + req.getMsgId() + "), but no ReportPublisher object is registered to handle it" ); else fZone.log.warn( "Received a SIF_Request for " + qo.getObjectName() + " (MsgId=" + req.getMsgId() + "), but no Publisher object is registered to handle it" ); SIFException sifEx = new SIFException( SIFErrorCategory.REQUEST_RESPONSE, SIFErrorCodes.REQRSP_INVALID_OBJ_3, "Agent does not support this object type", qo.getObjectName(), fZone ); sendErrorResponse( req,sifEx, renderAsVer, maxBufSize ); throw sifEx; } else { if( BuildOptions.PROFILED ) ProfilerUtils.profileStop(); return; } } } } boolean success = false; // DataObjectOutputStreamImpl out = null; JEN BaseObjectOutputStream out = null; SIFMessageInfo msgInfo = new SIFMessageInfo( req, fZone ); Query query = null; try { // Convert SIF_Request/SIF_Query into a Query object if( q != null ){ query = new Query( q ); } msgInfo.setSIFRequestObjectType( typ ); } catch( Throwable thr ) { fZone.log.error( "Could not parse SIF_Query element", thr ); SIFException sifEx = new SIFException( SIFErrorCategory.XML_VALIDATION, SIFErrorCodes.XML_MALFORMED_2, "Could not parse SIF_Query element", ADKStringUtils.getStackTrace(thr), fZone ); sendErrorResponse( req, sifEx, renderAsVer, maxBufSize ); throw sifEx; } try { // Create a stream the Publisher can write results to if( isReportObject ) out = ReportObjectOutputStreamImpl.newInstance(); else out = DataObjectOutputStreamImpl.newInstance(); out.initialize( fZone, query , req.getSourceId(), req.getMsgId(), renderAsVer, maxBufSize ); success = true; if( isReportObject ) { Condition cnd = query.hasCondition( ReportingDTD.SIF_REPORTOBJECT_REFID ); String refId = cnd == null ? null : cnd.getValue(); ((ReportPublisher)target).onReportRequest( refId,(ReportObjectOutputStream)out,query,fZone,msgInfo ); } else { ((Publisher)target).onRequest( (DataObjectOutputStream) out,query,fZone,msgInfo ); } // Notify MessagingListeners... for( MessagingListener ml : getMessagingListeners( fZone ) ){ ml.onMessageProcessed( SIFMessagingListener.SIF_REQUEST, msgInfo ); } } catch( SIFException se ) { // For a SIF_Request, a SIFException (other than a Transport Error) // does not mean to return an error ack but instead to return a // valid SIF_Response with a SIF_Error payload (see the SIF // Specification). Transport Errors must be returned to the ZIS so // that the message will be retried later. // if( se.getRetry() || se.getSIFErrorCategory() == SIFErrorCategory.TRANSPORT ) { String msgId = req.getMsgId(); msgIdCache.remove(msgId); success = false; //retry was requested, so we have to tell the output stream to not send an empty response out.deferResponse(); fZone.log.warn( "SIFException in " + ( isReportObject ? "ReportPublisher.onReportRequest":"Publisher.onRequest") + ": Retry was requested, so deferring response because of this error", se ); throw se; } fZone.log.warn( "SIFException in " + ( isReportObject ? "ReportPublisher.onReportRequest":"Publisher.onRequest"), se ); out.setError( se.getError() ); } catch( ADKException adke ) { String msgId = req.getMsgId(); msgIdCache.remove(msgId); // If retry requested, throw a Transport Error back to the ZIS // instead of returning a SIF_Error in the SIF_Response payload if( adke.getRetry() ) { success = false; // retry was requested, so we have to tell the output stream to not send an empty response out.deferResponse(); fZone.log.warn( "ADKException in " + ( isReportObject ? "ReportPublisher.onReportRequest":"Publisher.onRequest") + ": Retry was requested, so deferring response because of this error", adke ); throw adke; } fZone.log.error( "Exception in " + ( isReportObject ? "ReportPublisher.onReportRequest":"Publisher.onRequest"), adke ); // Return SIF_Error payload in SIF_Response SIF_Error err = new SIF_Error(); err.setSIF_Category( SIFErrorCategory.GENERIC ); err.setSIF_Code( SIFErrorCodes.GENERIC_GENERIC_ERROR_1 ); err.setSIF_Desc( adke.getMessage() ); err.setSIF_ExtendedDesc( ADKStringUtils.getStackTrace(adke) ); out.setError( err ); } catch( Throwable thr ) { fZone.log.error( "Exception in " + ( isReportObject ? "ReportPublisher.onReportRequest":"Publisher.onRequest"), thr ); SIF_Error err = new SIF_Error(); err.setSIF_Category( SIFErrorCategory.GENERIC ); err.setSIF_Code( SIFErrorCodes.GENERIC_GENERIC_ERROR_1 ); err.setSIF_Desc( "Agent could not process the SIF_Request at this time" ); err.setSIF_ExtendedDesc( ADKStringUtils.getStackTrace(thr) ); out.setError( err ); } finally { try { out.close(); } catch( Exception ignored ) { fZone.log.warn( "Ignoring exception in out.close()", ignored ); } try { out.commit(); } catch( Exception ignored ) { fZone.log.warn( "Ignoring exception in out.commit()", ignored ); } if( BuildOptions.PROFILED ) ProfilerUtils.profileStop(); } } private void sendErrorResponse(SIF_Request req, SIFException se, SIFVersion renderAsVer, int maxBufSize) throws ADKException { BaseObjectOutputStream out = DataObjectOutputStreamImpl.newInstance(); out.initialize( fZone, (ElementDef[])null, req.getSourceId(), req.getMsgId(), renderAsVer, maxBufSize ); SIF_Error err = new SIF_Error( se.getSIFErrorCategory(), se.getErrorCode(), se.getErrorDesc(), se.getErrorExtDesc() ); out.setError( err ); try { out.close(); } catch( Exception ignored ) { fZone.log.warn( "Ignoring exception in out.close()", ignored ); } try { out.commit(); } catch( Exception ignored ) { fZone.log.warn( "Ignoring exception in out.commit()", ignored ); } } /** * Checks out an EvDisp thread for the dispatchEvent method */ private EvDisp checkoutEvDisp( Event forEvent ) { EvDisp d = new EvDisp(); if( fEvDispCache == null ) fEvDispCache = new Hashtable(); fEvDispCache.put(forEvent,d); return d; } /** * Checks in an EvDisp thread previously obtained with checkoutEvDisp */ private void checkinEvDisp( Event forEvent ) { if( fEvDispCache != null ) fEvDispCache.remove(forEvent); } /** * Sends a message.<p> * * If the message is a SIF_Event or SIF_Response message it is persisted to * the Agent Local Queue (if enabled) to ensure reliable delivery. The * message is then sent. Upon successful delivery to the ZIS, the message * is removed from the queue. For all other message types the message is * sent immediately without being posted to the queue. * <p> * * If an exception occurs and a message has been persisted to the queue, * it is removed from the queue if possible (the queue is operational) and * the send operation fails. * <p> */ public SIF_Ack send( SIFMessagePayload msg ) throws ADKMessagingException, ADKTransportException { return send(msg,false); } public SIF_Ack send( SIFMessagePayload msg, boolean isPullMessage ) throws ADKMessagingException, ADKTransportException { if( fZone.fProtocolHandler == null ){ throw new ADKTransportException( "Zone is not connected", fZone ); } try { PolicyManager policyMan = PolicyManager.getInstance( fZone ); if( policyMan != null ){ policyMan.applyOutboundPolicy(msg, fZone ); } } catch( ADKException adkex ){ throw new ADKMessagingException( "Unable to apply outbound message policy: " + adkex, fZone, adkex ); } SIF_Ack ack = null; StringWriter buf = null; SIFWriter out = null; byte stage = 1; boolean queued = false; byte pload = 0; boolean cancelled = false; try { // Assign values to message header. SIF_Header hdr = msg.getHeader(); hdr.setSIF_Timestamp( Calendar.getInstance() ); hdr.setSIF_SourceId(fSourceId); hdr.setSIF_Security(secureChannel()); // NOTE: Feature Request #55 allows SIF_Request and SIF_Event messages to be // assigned a SIF_MsgId by the agent, so make sure to only assign a // GUID if there isn't already one. if( hdr.getSIF_MsgId() == null || hdr.getSIF_MsgId().trim().length() == 0 ) hdr.setSIF_MsgId(GUIDGenerator.makeGUID()); // ADK 1.5+: SIF_LogEntry requires that we *duplicate* the // header within the object payload. This is really the only // place we can do that and ensure that the SIF_Header and // the SIF_LogEntry/SIF_LogEntryHeader are identical. if( msg instanceof SIF_Event ) { SIF_Event ev = ((SIF_Event)msg); SIF_ObjectData od = (SIF_ObjectData)ev.getChild( InfraDTD.SIF_EVENT_SIF_OBJECTDATA ); SIF_EventObject eo = od == null ? null : od.getSIF_EventObject(); if( eo != null && eo.getObjectName().equals( "SIF_LogEntry" ) && eo.getAction().equals( "Add" ) ) { SIF_LogEntry logentry = (SIF_LogEntry)eo.getChild( InfraDTD.SIF_LOGENTRY ); if( logentry != null ) { // Once we implement cloneable, we should be able to call hdr.Clone(). SIF_LogEntyr // will use the real SIF_Header in ADK 2.0 SIF_LogEntryHeader sleh = new SIF_LogEntryHeader(); sleh.setSIF_Header( (SIF_Header)hdr.clone() ); logentry.setSIF_LogEntryHeader( sleh ); } } } if( !isPullMessage || ( ADK.debug & ADK.DBG_MESSAGING_PULL ) != 0 ) { msg.LogSend(fZone.log); } } catch( Throwable thr ) { throw new ADKMessagingException("MessageDispatcher could not assign outgoing message header: "+thr,fZone); } try { // Test /* ByteArrayOutputStream byteStream = new ByteArrayOutputStream (); SIFWriter writer = new SIFWriter(byteStream); writer.write(msg); writer.flush(); System.out.println(byteStream); */ // Convert message to a string buf = new StringWriter(); out = new SIFWriter(buf,fZone); out.write(msg); out.flush(); stage = 2; // SIF_Event and SIF_Response are posted to the agent local queue // if enabled, otherwise sent immediately. All other message types // are sent immediately even when the queue is enabled. // if( fQueue != null && ( msg instanceof SIF_Event || msg instanceof SIF_Response ) ) { fQueue.postMessage(msg); queued = true; } // NOTE: Keeping StringBuffer for now StringBuffer buf2 = new StringBuffer(); buf2.append( buf.toString() ); List<MessagingListener> msgList = null; // Notify MessagingListeners... pload = ADK.DTD().getElementType( msg.getElementDef().name() ); if( pload != SIFDTD.MSGTYP_ACK ) { msgList = getMessagingListeners( fZone ); if( msgList.size() > 0 ){ SIFMessageInfo msgInfo = new SIFMessageInfo( msg, fZone ); for( MessagingListener ml : msgList ){ try { if( !ml.onSendingMessage( pload, msgInfo, buf2 ) ){ cancelled = true; break; } } catch( Throwable ignored ) { // Log the error fZone.log.warn( "Error from message listener " + ml.getClass().getName() + " in onSendingMessage.", ignored ); } } } } if( !cancelled ) { // Send the message String ackStr = fZone.fProtocolHandler.send(buf2.toString()); // RawMessageListener JEN msgList = getMessagingListeners( fZone ); for ( MessagingListener listener : msgList ) { try { if (listener instanceof RawMessageListener) { ((RawMessageListener) listener).onUnparsedMessageReceived(pload, new StringBuffer(ackStr)); } } catch( Throwable ignored ) { // Log the error fZone.log.warn( "Error from message listener " + listener.getClass().getName() + " in onUnparsedMessageReceived.", ignored ); } } try { // Parse the results into a SIF_Ack ack = (SIF_Ack)fParser.parse(ackStr,fZone,isPullMessage ? SIFParser.FLG_EXPECT_INNER_ENVELOPE : 0 ); } catch (Exception parseEx) { if (isPullMessage && (parseEx instanceof ADKParsingException || parseEx instanceof SIFException || parseEx instanceof XMLStreamException )) { if( ( ADK.debug & ADK.DBG_MESSAGE_CONTENT ) != 0 ) { fZone.log.info("Parse Exception : " + parseEx.getMessage()); fZone.log.info("Unparsed ACK received from ZIF:"); fZone.log.info( ackStr ); } // The SIFParse was unable to parse this message. Try to create an appropriate // SIF_Ack, if SIFMessageInfo is able to parse enough of the message throw new PullMessageParseException( parseEx, ackStr, fZone ); } throw new ADKMessagingException( parseEx.getMessage(), fZone ); } if( ack != null ) { ack.message = msg; if( !isPullMessage || ( ADK.debug & ADK.DBG_MESSAGING_PULL ) != 0 ) { ack.LogRecv(fZone.log); } } // Notify MessagingListeners... if( msgList != null && msgList.size() > 0 ) { SIFMessageInfo msgInfo = new SIFMessageInfo( msg, fZone ); for( MessagingListener ml : msgList ) { try { ml.onMessageSent( pload, msgInfo, ack ); } catch( Throwable ignored ) { fZone.log.warn( "Error received from MessagingListener " + ml.getClass().getName() + " in onMessageSent()", ignored ); } } } } else { // Prepare a success SIF_Ack to return ack = msg.ackImmediate(); } } catch( ADKMessagingException me ) { throw me; } catch( ADKTransportException te ) { throw te; } catch( Throwable thr ) { if( stage == 1 ) throw new ADKMessagingException("MessageDispatcher could not convert outgoing infrastructure message to a string: "+thr,fZone); if( stage == 2 ) throw new ADKMessagingException("MessageDispatcher could not convert SIF_Ack response to an object: "+thr,fZone); } finally { // Removed queued message from queue if( queued ) { try { fQueue.removeMessage(msg.getMsgId()); } catch( Throwable ignored ) { } } try { if( buf != null ) buf.close(); out.close(); } catch( IOException ignored ) { } } return ack; } /** * Poll the ZIS for messages pending in the agent's queue. * <p> * * This method is typically called by a ProtocolHandler thread to perform a * periodic pull when the agent is running in pull mode. It may also be * called by the framework to force a pull if the framework requires an * immediate response to a message it has sent to the ZIS, such as a request * for SIF_ZoneStatus. * <p> * * If a message is retrieved from the ZIS, it is dispatched through the * ADK's usual message routing mechanism just as pushed messages are. Thus, * there is no difference between push and pull mode once a message has been * obtained from the ZIS. Because message routing is asynchronous (i.e. the * MessageDispatcher will forward the message to the appropriate framework * or agent code), this method does not return a value. If an error is * returned in the SIF_Ack, the agent's FaultListener will be notified if * one is registered. If an exception occurs, it is thrown to the caller. * <p> * * Each time this method is invoked it sends a SIF_GetMessage to the ZIS. * Calling this method repeatedly until 0 or -1 is returned effectively * empties out the agent's queue. * <p> * * @param threadRunLimit * limits the number of milliseconds this method will execute. * Messages are repeatedly pulled until the time limit is * exceeded or there are no more messages available. 0 means no * time limit. * * @return zero if no messages were waiting in the agent's queue; 1 if a * message was pulled from the agent's queue; -1 if the zone is * sleeping. */ public int pull() throws ADKException, LifecycleException { int messageCount = 0; // Wait for fPullCanStart to be set to true by the ZoneImpl class once // the zone is connected if( ( ADK.debug & ADK.DBG_MESSAGING_PULL ) != 0 ){ fZone.log.debug("Polling for next message..."); } // TODO: This should be in SIF_Primitives? // Send a SIF_GetMessage, get a SIF_Ack SIF_SystemControl sys = new SIF_SystemControl( fZone.getHighestEffectiveZISVersion() ); SIF_SystemControlData cmd = new SIF_SystemControlData(); cmd.addChild( new SIF_GetMessage() ); sys.setSIF_SystemControlData(cmd); SIF_Ack ack = null; try { // ACK received from ZIF JEN ack = send(sys,true); } catch( PullMessageParseException pmpe ){ // Unable to parse the pulled message. Try sending the proper // Error SIF_Ack to remove the message from the queue if( pmpe.fSourceMessage != null ){ fZone.getLog().debug( "Handling exception by creating a SIF_Error", pmpe.fParseException ); // Try parsing out the SIF_OriginalMsgId so that we can remove the message // from the queue. // Ack either the SIF_Ack or the internal, embedded message, based on our setting int startIndex = fAckAckOnPull ? 0 : 10; int messageStart = pmpe.fSourceMessage.indexOf( "<SIF_Message", startIndex ); SIFException sourceException = null; if( pmpe.fParseException instanceof SIFException ){ sourceException = (SIFException)pmpe.fParseException; }else { sourceException = new SIFException( SIFErrorCategory.XML_VALIDATION, SIFErrorCodes.XML_GENERIC_ERROR_1, "Unable to parse pulled SIF_Message", pmpe.fParseException.getMessage(), fZone, pmpe.fParseException ); } SIF_Ack errorAck = SIFPrimitives.ackError( pmpe.fSourceMessage.substring( messageStart ), sourceException, fZone ); errorAck.setSIFVersion( sys.getSIFVersion() ); // Sending Error ACK to ZIF JEN send( errorAck ); } } // // Process the response. If status code 9 (no message), no // action is taken. If status code 0 (success), the content of // the SIF_Status / SIF_Data element is parsed and dispatched. // If an error is reported in the ack, the agent's fault // handler is called with a SIFException describing the error // if a fault handler has been registered. // if( ack.hasStatusCode( SIFStatusCodes.NO_MESSAGES_9 ) ) { if( ( ADK.debug & ADK.DBG_MESSAGING_PULL ) != 0 ) fZone.log.debug("No messages waiting in agent queue"); return 0; } if( ack.hasError() ) { SIFException se = new SIFException( ack, fZone ); fZone.log.debug("Unable to pull the next message from the queue: " + se.toString() ); ADKUtils._throw( se, fZone.log ); } if( ack.hasStatusCode( SIFStatusCodes.SUCCESS_0 ) ) { messageCount++; ADKException parseEx = null; SIFMessagePayload payload = getPullMessagePayload( ack ); if( ( ADK.debug & ( ADK.DBG_MESSAGING | ADK.DBG_MESSAGING_PULL ) ) != 0 ) { fZone.log.debug("Pulled a "+payload.getElementDef().tag( payload.getSIFVersion() )+" message (SIF " + payload.getSIFVersion() + ")" ); } // Notify MessagingListeners... boolean cancelled = false; List<MessagingListener> msgList = MessageDispatcher.getMessagingListeners( fZone ); if( msgList != null && msgList.size() > 0 ) { StringWriter tmp = new StringWriter(); SIFWriter sifwriter = new SIFWriter(tmp,fZone); sifwriter.write( payload ); sifwriter.flush(); tmp.flush(); StringBuffer xml = new StringBuffer(); xml.append( tmp.toString() ); // Determine message type before parsing for( MessagingListener ml : msgList ) { try { byte pload = ADK.DTD().getElementType( payload.getElementDef().name() ); byte code = ml.onMessageReceived( pload, xml ); switch( code ) { case MessagingListener.RX_DISCARD: cancelled = true; break; case MessagingListener.RX_REPARSE: { try { // Reparse the XML into a new message payload = (SIFMessagePayload)fParser.parse( xml.toString(), fZone ); } catch( IOException ioe ) { parseEx = new ADKException( "Failed to reparse message that was modified by MessagingListener: " + ioe, fZone ); } } break; } } catch( ADKException adke ) { parseEx = adke; } } } if( fQueue != null ) { // TODO: put message on agent local queue } else { if( parseEx != null ) throw parseEx; int ackStatus = SIFStatusCodes.IMMEDIATE_ACK_1; SIFException err = null; boolean acknowledge = true; try { // Dispatch the message if( !cancelled ) ackStatus = dispatch(payload); } catch( LifecycleException le ) { throw le; } catch( SIFException se ) { err = se; } catch( ADKException adke ) { // TODO: This needs to generate proper category/code based on payload if( adke.hasSIFExceptions() ) { // Return the first exception err = adke.getSIFExceptions()[0]; } else { // Build a SIFException to describe this ADKException err = new SIFException( SIFErrorCategory.GENERIC, SIFErrorCodes.GENERIC_GENERIC_ERROR_1, adke.getMessage(), ADKStringUtils.getStackTrace(adke), fZone ); } } catch( Throwable thr ) { // Uncaught exception (probably an ADK internal error) String txt = "An unexpected error occurred while processing a pulled message: " + thr; fZone.log.debug( txt, thr ); // Build a SIFException to describe this Throwable err = new SIFException( SIFErrorCategory.SYSTEM, SIFErrorCodes.SYS_GENERIC_ERROR_1, thr.getMessage(), ADKStringUtils.getStackTrace(thr), fZone ); } if( acknowledge ) { sendPushAck(ack, payload, ackStatus, err); } else return 1; } } else { // We only get to here if there is no error and no success code if( ack.hasStatusCode( SIFStatusCodes.SLEEPING_8 ) ) { // Zone is sleeping return -1; } else { // Unknown condition ADKUtils._throw( new SIFException(ack, fZone ), fZone.getLog() ); } } return messageCount>0 ? 1 : 0; } private boolean threadTimeExceeded (long start, int threadRunLimit) { boolean retval = false; if (threadRunLimit != 0) { long now = Calendar.getInstance().getTimeInMillis(); if ( (now-start) >= threadRunLimit ) { retval = true; // System.out.println("Thread Timeout exceeded. Start-" + start + " Now- " + now + " Limit-" + threadRunLimit); } } return retval; } /** * Sends a SIF_Ack in response to a pulled message * @param sifGetMessageAck The original SIF_Ack from the SIF_GetMessage. This is sometimes null, when * parsing fails * @param pulledMEssage The message delivered inside of the above ack. NOTE that even if parsing fails, * the SIFParser tries to return what it can, and will return this message payload (in getParsed()), * instead of the above container message. * @param ackStatus The status to ack (NOTE: This is ignored if the err property is set) * @param err The error to set in the SIF_Ack */ private void sendPushAck(SIF_Ack sifGetMessageAck, SIFMessagePayload pulledMEssage, int ackStatus, SIFException err) { try { SIF_Ack ack2 = null; if( fAckAckOnPull && sifGetMessageAck != null){ ack2 = sifGetMessageAck.ackStatus( ackStatus ); } else { ack2 = pulledMEssage.ackStatus(ackStatus); } // If an error occurred processing the message, return // the details in the SIF_Ack if( err != null ) { fZone.log.debug( "Handling exception by creating a SIF_Error", err ); SIF_Error newErr = new SIF_Error(); newErr.setSIF_Category( err.getSIFErrorCategory() ); newErr.setSIF_Code( err.getErrorCode() ); newErr.setSIF_Desc( err.getErrorDesc() ); newErr.setSIF_ExtendedDesc( err.getErrorExtDesc() ); ack2.setSIF_Error( newErr ); // Get rid of the <SIF_Status> SIF_Status status = ack2.getSIF_Status(); if( status != null ) { ack2.removeChild( status ); } } // ACK sent back to ZIF JEN // Send the ack send(ack2); } catch( Exception ackEx ) { fZone.log.debug( "Failed to send acknowledgement to pulled message: " + ackEx, ackEx ); } } private SIFMessagePayload getPullMessagePayload( SIF_Ack sifPullAck ) throws SIFException { try { // Get the next message SIFElement msg = sifPullAck.getSIF_Status().getSIF_Data().getChildren()[0]; return (SIFMessagePayload)msg.getChildren()[0]; } catch( Exception ex ) { // TT 139 Andy E //An Exception occurred while trying to read the contents of the SIF_Ack throw new SIFException( SIFErrorCategory.XML_VALIDATION , SIFErrorCodes.XML_MISSING_MANDATORY_ELEMENT_6, "Unable to parse SIF_Ack", fZone, ex ); } } public SIF_Security secureChannel() { AgentProperties props = fZone.getProperties(); SIF_SecureChannel sec = new SIF_SecureChannel( AuthenticationLevel.wrap( String.valueOf( props.getAuthenticationLevel() ) ), EncryptionLevel.wrap( String.valueOf( props.getEncryptionLevel() ) ) ); return new SIF_Security(sec); } public void shutdown() throws ADKException { fRunning = null; } public void run() { fRunning = new Object(); SIFMessageInfo messages[] = null; while( fRunning != null ) { try { // Wait for the next incoming message from any zone and of any type messages = fQueue.nextMessage( IAgentQueue.MSG_ANY, IAgentQueue.INCOMING); // Dispatch all incoming messages... for( int i = 0; i < messages.length; i++ ) { try { SIFMessagePayload msg = null; dispatch(msg); fQueue.removeMessage(messages[i].getMsgId()); } catch( Exception adke ) { System.out.println("Messenger could not dispatch message from Agent Local Queue (agent: \""+fZone.getAgent().getId()+"\", zone: \""+fZone.getZoneId()+"\": "); adke.printStackTrace( System.out ); } } } catch( Exception adke ) { System.out.println("MessageDispatcher could not get next message from Agent Local Queue (agent: \""+fZone.getAgent().getId()+"\", zone: \""+fZone.getZoneId()+"\": "+adke); adke.printStackTrace( System.out ); } } } public class MessageIdCache extends LinkedHashMap<String, String> { private static final long serialVersionUID = 1L; @Override protected boolean removeEldestEntry(Entry<String, String> eldest) { if (this.size() > 10) { return true; } return false; } } /** * EvDisp thread handles a single dispatch request. Used when ALQ is disabled. */ public class EvDisp implements Runnable { EvState _state = new EvState(); // state of dispatch Object _idle = new Object(); // signaled when a request received boolean _alive = false; // flag thread is alive Subscriber _target; Event _event; ZoneImpl _zone; Topic _topic; MessageInfo _msgInfo; SMBHelper _smb; /** * Constructs an EvDisp and starts its thread */ public EvDisp() { new Thread(this).start(); synchronized(this) { try { while( !_alive ) wait(); } catch( Throwable ignored ) { if( ( ADK.debug & ADK.DBG_MESSAGING_EVENT_DISPATCHING ) != 0 ) fZone.log.debug("EvDisp interrupted waiting for thread to run: "+ignored); } } } /** * Signals the waitForAckCode method that the processing of the * dispatch request is ready for MessageDispatcher to return a * SIF_Ack with this code. */ public void notifyAckCode( int code ) { synchronized( _state ) { _state._ack = code; _state._exception = null; } synchronized( _idle ) { _idle.notifyAll(); } } /** * Signals the waitForAckCode method that an exception occurred * during the processing of the dispatch request. It should stop * waiting for a code and instead throw the exception. */ public void notifyException( Throwable thr ) { synchronized( _state ) { _state._ack = -1; _state._exception = thr; } synchronized( _idle ) { _idle.notifyAll(); } } /** * Waits for the EvDisp thread to either signal that an Intermediate * acknowledgement should be returned to the ZIS, signal that processing * has been completed and an Immediate acknowledgement should be * returned, or signal that an exception occurred. If an exception * occurred, it is rethrown by this method. * * @return The SIF_Ack code (either 1 or 2) that should be returned to * the ZIS in response to the processing of the SIF_Event */ private int waitForAckCode() throws ADKMessagingException { int result = -1; try { synchronized( _idle ) { // Wait for the state to be set if( ( ADK.debug & ADK.DBG_MESSAGING_EVENT_DISPATCHING ) != 0 ) fZone.log.debug( "EvDisp waiting for acknowlegement code" ); while( _state._exception == null && _state._ack == -1 ) _idle.wait(1000); if( ( ADK.debug & ADK.DBG_MESSAGING_EVENT_DISPATCHING ) != 0 ) fZone.log.debug( "EvDisp done waiting for acknowlegement code" ); } synchronized( _state ) { // Was it changed because of an exception? If so throw it if( _state._exception != null ) { if( ( ADK.debug & ADK.DBG_MESSAGING ) != 0 ) fZone.log.debug( "EvDisp received an exception instead of an acknowledgement code" ); if( _state._exception instanceof ADKMessagingException ) { ADKUtils._throw( (ADKMessagingException)_state._exception,fZone.log ); } else { ADKMessagingException adkme = new ADKMessagingException( "Dispatching SIF_Event: " + _state._exception, fZone, _state._exception ); if( _state._exception instanceof ADKException ) { // ensure that retry support is always enabled adkme.setRetry( ((ADKException) _state._exception).getRetry() ); } ADKUtils._throw( adkme ,fZone.log ); } } // Return the SIF_Ack code the caller is waiting for if( ( ADK.debug & ADK.DBG_MESSAGING ) != 0 ) fZone.log.debug( "EvDisp received acknowledgement code "+_state._ack ); result = _state._ack; } } catch( InterruptedException ie ) { if( ( ADK.debug & ADK.DBG_MESSAGING_EVENT_DISPATCHING ) != 0 ) fZone.log.debug( "EvDisp interrupted waiting for ack code" ); ADKUtils._throw( new ADKMessagingException(ie.toString(),fZone), fZone.log ); } return result; } /** * Wakes up the thread, dispatches the event to Subscriber.onEvent, * then returns the appropriate SIF_Ack code. If onEvent() completes * processing without invoking SMB, this method returns once onEvent() * is finished. If, however, onEvent() invokes SMB by instantiating a * TrackQueryResults object, this method returns at the time the * TrackQueryResults constructor is called. */ public int dispatch( Subscriber target, Event event, ZoneImpl zone, Topic topic, MessageInfo msgInfo ) throws ADKMessagingException { _target=target; _event=event; _zone=zone; _topic=topic; _msgInfo=msgInfo; _smb=null; _state._ack = -1; _state._exception = null; synchronized( _idle ) { if( ( ADK.debug & ADK.DBG_MESSAGING ) != 0 ) fZone.log.debug( "MessageDispatcher giving EvDisp a dispatch request" ); _idle.notifyAll(); } return waitForAckCode(); } public void run() { synchronized(this) { _alive = true; notifyAll(); } // Wait for this thread to get dispatch info synchronized( _idle ) { try { if( ( ADK.debug & ADK.DBG_MESSAGING ) != 0 ) fZone.log.debug( "EvDisp waiting for dispatch request..." ); while( _target == null ) _idle.wait(); } catch( InterruptedException ie ) { return; } } try { if( ( ADK.debug & ADK.DBG_MESSAGING ) != 0 ) fZone.log.debug( "EvDisp received dispatch request" ); // Dispatch event to subscriber... _target.onEvent(_event,_zone,_msgInfo); // Notify MessagingListeners... List<MessagingListener> msgList = getMessagingListeners( fZone ); if( msgList != null && msgList.size() > 0 ) { for( MessagingListener ml : msgList){ ml.onMessageProcessed( SIFMessagingListener.SIF_EVENT, _msgInfo ); } } // The event was successfully processed, so either send an // immediate SIF_Ack with status code 1, *or* if SMB was // invoked by a TrackQueryResults object, ask the SMBHelper // to send a final SIF_Ack with status code 3. // if( _smb == null ) { notifyAckCode(1); // send SIF_Ack(1) } else { _smb.endSMB(); // send SIF_Ack(3) } } catch( SIFException se ) { Thread.currentThread().interrupt(); notifyException(se); } catch( ADKException adke ) { Thread.currentThread().interrupt(); notifyException(adke); } catch( Throwable thr ) { Thread.currentThread().interrupt(); fZone.log.error( "Uncaught exception in onEvent: " + thr, thr ); notifyException(thr); } } } class EvState { int _ack = -1; Throwable _exception = null; } static class PullMessageParseException extends ADKMessagingException { Exception fParseException; String fSourceMessage; public PullMessageParseException( Exception parseException, String sourceMessage, Zone zone ) { super( parseException.getMessage(), zone ); fSourceMessage = sourceMessage; fParseException = parseException; } } /** * Implements RequestInfo for cases where a cache lookup fails. The ADK * always guarantees that SIFMessageInfo.getSIFRequestInfo() will be a non-null * value in the onQueryResults and onQueryPending message handlers * * @author Andrew Elmhorst * */ class UnknownRequestInfo implements RequestInfo { private String fMsgId; private String fObjectType; public UnknownRequestInfo( String msgId, String objectType ) { fMsgId = msgId; fObjectType = objectType; } public String getObjectType() { return fObjectType; } public String getMessageId() { return fMsgId; } public Date getRequestTime() { return null; } public boolean isActive() { return false; } public Serializable getUserData() { return null; } } private void logAndThrowSIFException( String shortMessage, Throwable exception ) throws SIFException { if ( (ADK.debug & ADK.DBG_EXCEPTIONS) != 0 ) fZone.log.error( shortMessage, exception ); SIFException exToThrow = new SIFException( SIFErrorCategory.GENERIC, SIFErrorCodes.GENERIC_GENERIC_ERROR_1, shortMessage, ADKStringUtils.getStackTrace( exception ), fZone ); if ( (ADK.debug & ADK.DBG_EXCEPTIONS) != 0 ) fZone.log.error( "Translated to a SIFException", exToThrow ); throw exToThrow; } private void logAndRethrow( String shortMessage, ADKException exception ) throws ADKException { if ( (ADK.debug & ADK.DBG_EXCEPTIONS) != 0 ) { fZone.log.error( shortMessage, exception ); } throw exception; } private void logAndThrowRetry( String shortMessage, Throwable exception ) throws ADKException { if ( (ADK.debug & ADK.DBG_EXCEPTIONS) != 0 ) { fZone.log.error( shortMessage, exception ); } SIFException exToThrow = new SIFException( SIFErrorCategory.TRANSPORT, SIFErrorCodes.WIRE_GENERIC_ERROR_1, shortMessage, ADKStringUtils.getStackTrace( exception ), fZone ); if ( (ADK.debug & ADK.DBG_EXCEPTIONS) != 0 ) fZone.log.error( "Translated to a SIFException that will force a retry", exToThrow ); throw exToThrow; } }