//
// Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s).
// All rights reserved.
//
package openadk.library.services;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import openadk.library.ADK;
import openadk.library.ADKException;
import openadk.library.ElementDef;
import openadk.library.Query;
import openadk.library.SIFDataObject;
import openadk.library.SIFElement;
import openadk.library.Zone;
import openadk.library.common.YesNo;
import openadk.library.infra.SIF_Error;
import openadk.library.services.impl.ServiceObjectOutputStreamImpl;
import openadk.library.services.impl.ServiceOutputFileStream;
import openadk.library.services.impl.ServiceOutputStreamImpl;
/**
* Helper class to send SIF_Response messages when an agent defers automatic
* sending of responses in its implementation of the Publisher.onRequest message
* handler. This class is designed to be used when the
* {@link openadk.library.DataObjectOutputStream#deferResponse}
* method is called during SIF_Request processing, and must be used outside of
* the Publisher.onRequest message handler.
* <p>
*
* By design, the ADK automatically sends one or more SIF_Response messages to
* the zone when control is returned from the Publisher.onRequest message
* handler. SIF_Responses are sent in a background thread without any further
* intervention on the agent's part. The ADK takes care of proper data object
* rendering and packetizing based on the parameters of the SIF_Request message,
* and includes a SIF_Error element in the payload if an exception is thrown
* from the onRequest method.
* <p>
*
* Although this behavior is convenient and very appropriate for most agent
* implementations, there are cases when agents require greater control over SIF
* Request & Response messaging. Specifically, agents sometimes need the
* ability to decouple SIF_Response processing from SIF_Request processing. For
* example, implementing the SIF 1.5 StudentLocator message choreography may
* require that SIF_Response messages are sent hours or days after the initial
* SIF_Request is received. Or, an agent might queue requests in a database
* table and work on them later when it has available resources. Both of these
* scenarios can be achieved by calling the
* {@link openadk.library.DataObjectOutputStream#deferResponse}
* method on the DataObjectOutputStream passed to the Publisher.onRequest
* message handler. This method signals the ADK to ignore any SIFDataObjects
* written to the stream and to defer the sending of SIF_Response messages. The
* agent must send its own SIF_Response messages at a later time by using this
* SIFResponseSender helper class
* <p>
*
* To use this class,
* <p>
*
* <ul>
* <li> When the agent is ready to send SIF_Response messages for a SIF_Request
* that was received at an earlier time, instantiate a SIFResponseSender. </li>
* <li> Call the {@link #open} method and pass it the Zone you wish to send
* SIF_Response messages to. You must also pass the SIF_Version and
* SIF_MaxBufferSize value from the original SIF_Request message. (Be sure to
* obtain these values from the <code>SIFMessageInfo</code> parameter in your
* Publisher.onRequest implementation so you can pass them to the {@link #open}
* method when using this class.) </li>
* <li> Repeatedly call the {@link #write(SIFDataObject)} method, once for each
* SIFDataObject that should be included in the SIF_Response stream </li>
* <li> Call the {@link #write(SIF_Error)} method to include a SIF_Error element
* in the SIF_Response stream </li>
* <li> When finished, call the {@link #close} method. The ADK will
* automatically package the objects into one or more SIF_Response packets in
* the same way it does for normal request/response processing via the
* Publisher.onRequest method. </li>
* </ul>
*
* @since ADK 1.5.1
*/
public class SIFServiceOutputSender {
protected Zone fZone;
protected ServiceObjectOutputStream fOut = null;
protected String operation = "";
/**
* Open the SIFResponseSender to send SIF_Response messages to a specific
* zone.
*
* @param zone
* The zone to send messages to
*
* @param sifRequestMsgId
* The SIF_MsgId from the original SIF_Request message. You can
* obtain this by calling
* {@link openadk.library.SIFMessageInfo#getMsgId} on
* the SIFMessageInfo parameter passed to the Publisher.onRequest
* method. NOTE: Do not call
* {@link openadk.library.SIFMessageInfo#getSIFRequestMsgId()}
* as it will return a <code>null</code> value and is only
* intended to be called on SIF_Response messages.
*
* @param sifRequestSourceId
* The SIF_SourceId from the original SIF_Request message. You
* can obtain this by calling
* {@link openadk.library.SIFMessageInfo#getSourceId()}
* on the SIFMessageInfo parameter passed to the
* Publisher.onRequest method.
*
* @param sifVersion
* The SIF_Version value from the original SIF_Request message.
* You can obtain this by calling
* {@link openadk.library.SIFMessageInfo#getSIFRequestVersion}
* on the SIFMessageInfo parameter passed to the
* Publisher.onRequest method.
*
* @param maxBufSize
* The SIF_MaxBufferSize value from the original SIF_Request
* message. You can obtain this by calling
* {@link openadk.library.SIFMessageInfo#getMaxBufferSize}
* on the SIFMessageInfo parameter passed to the
* Publisher.onRequest method.
*
* @param fieldRestrictions
* The Query object representing the SIF_Request that this
* SIF_Response is being generated for
*
* @exception IllegalArgumentException
* is thrown if any of the parameter are invalid
*
* @exception ADKException
* is thrown if an error occurs preparing the output stream
*/
public void open(Zone zone, ServiceOutputInfo serviceOutputInfo) throws ADKException {
fZone = zone;
this.setOperation(serviceOutputInfo.getOperation());
ElementDef serviceDef = ADK.DTD().lookupElementDef( serviceOutputInfo.getService() );
ServiceOutputFileStream serviceStream = (ServiceOutputFileStream)ServiceOutputStreamImpl.newInstance();
serviceStream.initialize(zone, (Query) null, serviceOutputInfo.getSIFRequestSourceId(), serviceOutputInfo .getSIFRequestMsgId(), serviceOutputInfo.getSIFVersion(), serviceOutputInfo.getSIFMaxBufferSize());
serviceStream.setServiceOutputInfo(serviceOutputInfo);
fOut = new ServiceObjectOutputStreamImpl(serviceStream, serviceDef, serviceOutputInfo.getOperation() );
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
String bodyHeader = "<" + getOperation() + "Response> ";
try {
buffer.write(bodyHeader.getBytes());
// JEN fOut.writeBuffer(buffer);
}
catch (IOException e) {
throw new ADKException(e.getMessage(), fZone);
}
}
/**
* Write a SIFDataObject to the output stream
*/
public void write(SIFElement sdo) throws ADKException {
_checkOpen();
try {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
String newLine = " \n";
buffer.write(newLine.getBytes());
fOut.writeBuffer(buffer);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
fOut.write(sdo);
}
/**
* Write a SIF_Error to the output stream
*
* @param error
* A SIF_Error instance
*/
public void write(SIF_Error error) throws ADKException {
_checkOpen();
fOut.setError(error);
}
/**
* Close the stream and send one or more SIF_Response packets to the zone.
*/
public void close() throws ADKException {
_checkOpen();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
String bodyHeader = "</" + getOperation() + "Response> ";
try {
buffer.write(bodyHeader.getBytes());
// JEN fOut.writeBuffer(buffer);
fOut.close();
}
catch (IOException ioe) {
throw new ADKException("Failed to close SIFResponseSender stream: "
+ ioe, fZone);
}
fOut.commit();
}
private void _checkOpen() {
if (fOut == null)
throw new IllegalStateException("SIFResponseSender is not open");
}
/**
* Allows the starting packet number for SIF_Responses to be set.
* <p>
*
* By default, the SIFResponseSender class will automatically set the
* starting packet number to 1 and increment the number automatically for
* each packet. However, some agents may need to respond to SIF_Requests
* with multiple, asynchronous responses. In that case, the agent developer
* is responsible for keeping track of the packet numbers that were
* previously sent and setting the correct starting packet number for the
* next set of SIF_Response packtets.
*
* @see #getSIF_PacketNumber()
* @param packetNumber
* The SIF_PacketNumber value that should be set for the next
* SIF_Response packet
*
* @exception IllegalStateException
* thrown if this property is set after objects have already
* been written
*/
public void setSIF_PacketNumber(int packetNumber) {
_checkOpen();
fOut.setSIF_PacketNumber(packetNumber);
}
/**
* Allows the SIF_MorePackets value for SIF_Responses to be set.
* <p>
*
* By default, the SIFResponseSender class will automatically close out the
* SIF_Response stream when SIFResponseSender is closed by setting the
* SIF_MorePackets value to "No" on the final packet. However, if the
* SIF_Response stream should be kept open, this value will be used on the
* last SIF_Response packet that is generated by the SIFResponseSender
* class.
*
* @param morePacketsValue
*/
public void setSIF_MorePackets(YesNo morePacketsValue) {
_checkOpen();
fOut.setSIF_MorePackets(morePacketsValue);
}
/**
* Gets the SIF_PacketNumber of the current SIF_Response packet
*
* @return
*/
public int getSIF_PacketNumber() {
_checkOpen();
return fOut.getSIF_PacketNumber();
}
/**
* Gets the value that will be set on the final SIF_Response packet when the
* SIFResponseSender is closed.
*
* @return
*/
public YesNo getSIF_MorePackets() {
_checkOpen();
return fOut.getSIF_MorePackets();
}
public String getOperation() {
return operation;
}
public void setOperation(String operation) {
this.operation = operation;
}
}