ServerTransactionpublic class ServerTransaction extends Transaction implements SIPServerRequestInterfaceRepresents a server transaction. |
Fields Summary |
---|
protected int | collectionTimeCollection time. | private SIPServerRequestInterface | requestOfReal RequestInterface to pass messages to. | protected boolean | isMappedFlag indicating this transaction is known to the stack. |
Constructors Summary |
---|
protected ServerTransaction(SIPTransactionStack newSIPMessageStack, MessageChannel newChannelToHeaderUse)Creates a new server transaction.
super(newSIPMessageStack, newChannelToHeaderUse);
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"Creating Server Transaction" + this);
// new Exception().printStackTrace();
}
|
Methods Summary |
---|
protected void | fireRetransmissionTimer()Called by the transaction stack when a retransmission
timer fires. This retransmits the last response when the
retransmission filter is enabled.
try {
// Resend the last response sent by this transaction
if (isInviteTransaction() &&
((SIPTransactionStack)getSIPStack()).retransmissionFilter)
getMessageChannel().sendMessage(lastResponse);
} catch (IOException e) {
raiseErrorEvent
(SIPTransactionErrorEvent.TRANSPORT_ERROR);
}
| protected void | fireTimeoutTimer()Called by the transaction stack when a timeout timer fires.
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"ServerTransaction.fireTimeoutTimer "
+ this.getState() + " method = " +
this.getOriginalRequest().getMethod());
}
Dialog dialog = (Dialog) this.getDialog();
int mystate = this.getState();
if (((SIPTransactionStack)getSIPStack()).isDialogCreated
(this.getOriginalRequest().getMethod()) &&
(mystate == super.CALLING_STATE ||
mystate == super.TRYING_STATE)) {
dialog.setState(Dialog.TERMINATED_STATE);
} else if (getOriginalRequest().getMethod().equals(Request.BYE)) {
if (dialog != null)
dialog.setState(Dialog.TERMINATED_STATE);
}
if ((getState() == CONFIRMED_STATE ||
getState() == COMPLETED_STATE) &&
isInviteTransaction()) {
raiseErrorEvent
(SIPTransactionErrorEvent.TIMEOUT_ERROR);
setState(TERMINATED_STATE);
} else if (! isInviteTransaction() && (
getState() == COMPLETED_STATE ||
getState() == CONFIRMED_STATE)) {
setState(TERMINATED_STATE);
} else if (isInviteTransaction() &&
getState() == TERMINATED_STATE) {
// This state could be reached when retransmitting
raiseErrorEvent(SIPTransactionErrorEvent.TIMEOUT_ERROR);
if (dialog != null) dialog.setState(Dialog.TERMINATED_STATE);
}
| public Response | getLastResponse()Gets the last response.
return this.lastResponse;
| public java.lang.String | getProcessingInfo()Gets the processing infromation.
return requestOf.getProcessingInfo();
| public MessageChannel | getResponseChannel()Returns this transaction.
return this;
| public java.lang.String | getViaHost()Gets the via host name.
return encapsulatedChannel.getViaHost();
| public int | getViaPort()Gets the via port number.
return encapsulatedChannel.getViaPort();
| public boolean | isMessagePartOfTransaction(Message messageToHeaderTest)Deterines if the message is a part of this transaction.
// List of Via headers in the message to test
ViaList viaHeaders;
// ToHeaderpmost Via header in the list
ViaHeader topViaHeader;
// Branch code in the topmost Via header
String messageBranch;
// Flags whether the select message is part of this transaction
boolean transactionMatches;
transactionMatches = false;
// Compensation for retransmits after OK has been dispatched
// as suggested by Antonis Karydas.
if ((((SIPTransactionStack)getSIPStack()).isDialogCreated
(((Request)messageToHeaderTest).getMethod()))
|| !isTerminated()) {
// Get the topmost Via header and its branch parameter
viaHeaders = messageToHeaderTest.getViaHeaders();
if (viaHeaders != null) {
topViaHeader = (ViaHeader)viaHeaders.getFirst();
messageBranch = topViaHeader.getBranch();
if (messageBranch != null) {
// If the branch parameter exists but
// does not start with the magic cookie,
if (!messageBranch.toUpperCase().startsWith(SIPConstants.
GENERAL_BRANCH_MAGIC_COOKIE.toUpperCase())) {
// Flags this as old
// (RFC2543-compatible) client
// version
messageBranch = null;
}
}
// If a new branch parameter exists,
if (messageBranch != null &&
this.getBranch() != null) {
if (getBranch().equals(messageBranch)
&& topViaHeader.getSentBy().
equals(((ViaHeader)getOriginalRequest().
getViaHeaders().getFirst()).
getSentBy())) {
// Matching server side transaction with only the
// branch parameter.
transactionMatches = true;
}
// If this is an RFC2543-compliant message,
} else {
// If RequestURI, ToHeader tag, FromHeader tag,
// CallIdHeader, CSeqHeader number, and top Via
// headers are the same,
String originalFromHeaderTag =
getOriginalRequest().getFromHeader().
getTag();
String thisFromHeaderTag =
messageToHeaderTest.getFromHeader().getTag();
boolean skipFromHeader =
(originalFromHeaderTag == null ||
thisFromHeaderTag == null);
String originalToHeaderTag =
getOriginalRequest().getTo().
getTag();
String thisToHeaderTag =
messageToHeaderTest.getTo().getTag();
boolean skipToHeader =
(originalToHeaderTag == null ||
thisToHeaderTag == null);
if (getOriginalRequest().
getRequestURI().
equals(((Request)messageToHeaderTest).
getRequestURI()) &&
(skipFromHeader ||
originalFromHeaderTag.equals(thisFromHeaderTag)) &&
(skipToHeader ||
originalToHeaderTag.equals(thisToHeaderTag)) &&
getOriginalRequest().
getCallId().getCallId().
equals(messageToHeaderTest.getCallId()
.getCallId()) &&
getOriginalRequest().
getCSeqHeader().getSequenceNumber() ==
messageToHeaderTest.getCSeqHeader().
getSequenceNumber() &&
topViaHeader.equals(
getOriginalRequest().
getViaHeaders().getFirst())) {
transactionMatches = true;
}
}
}
}
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"TRANSACTION MATCHES:" + transactionMatches);
}
return transactionMatches;
| public boolean | isTransactionMapped()Returns true if the transaction is known to stack.
return this.isMapped;
| protected void | map()Sends out a trying response (only happens when the transaction is
mapped). Otherwise the transaction is not known to the stack.
if (getState() == -1 ||
getState() == TRYING_STATE) {
if (isInviteTransaction() && ! this.isMapped) {
this.isMapped = true;
// Has side-effect of setting
// state to "Proceeding"
new SendTrying(this);
} else {
isMapped = true;
}
}
| public void | processRequest(Request transactionRequest, MessageChannel sourceChannel)Processes a new request message through this transaction.
If necessary, this message will also be passed onto the TU.
IMPL_NOTE:
Receiving the PUBLISH request at ESC (i.e. UAS):
The event state is identified by three major pieces:
Request-URI, event-type and entity-tag (RFC3903, section 4.1).
Maybe it is needed to maintain the event state vector in our
implementation.
boolean toTu = false;
try {
// If this is the first request for this transaction,
if (getState() == -1) {
// Save this request as the one this
// transaction is handling
setOriginalRequest(transactionRequest);
setState(TRYING_STATE);
toTu = true;
if (isInviteTransaction() && this.isMapped) {
// Has side-effect of setting
// state to "Proceeding".
sendMessage(transactionRequest.
createResponse(100, "Trying"));
}
// If an invite transaction is ACK'ed while in
// the completed state,
} else if (isInviteTransaction()
&& COMPLETED_STATE == getState()
&& transactionRequest.getMethod().equals(Request.ACK)) {
setState(CONFIRMED_STATE);
disableRetransmissionTimer();
if (!isReliable()) {
if (this.lastResponse != null
&& this.lastResponse.getStatusCode()
== Response.REQUEST_TERMINATED) {
setState(TERMINATED_STATE);
} else {
enableTimeoutTimer(TIMER_I);
}
} else {
setState(TERMINATED_STATE);
}
// Application should not Ack in CONFIRMED state
return;
} else if (transactionRequest.getMethod().equals
(getOriginalRequest().getMethod())) {
if (getState() == PROCEEDING_STATE ||
getState() == COMPLETED_STATE) {
// Resend the last response to
// the client
if (lastResponse != null) {
try {
// Send the message to the client
getMessageChannel().sendMessage
(lastResponse);
} catch (IOException e) {
setState(TERMINATED_STATE);
throw e;
}
}
} else if (transactionRequest.getMethod().
equals(Request.ACK)) {
// This is passed up to the TU to suppress
// retransmission of OK
requestOf.processRequest
(transactionRequest, this);
}
return;
}
// Pass message to the TU
if (COMPLETED_STATE != getState()
&& TERMINATED_STATE != getState()
&& requestOf != null) {
if (getOriginalRequest().getMethod()
.equals(transactionRequest.getMethod())) {
// Only send original request to TU once!
if (toTu)
requestOf.processRequest(transactionRequest,
this);
} else {
requestOf.processRequest(transactionRequest,
this);
}
} else {
// need revisit
// I am allowing it through!
if (((SIPTransactionStack) getSIPStack()).isDialogCreated(
getOriginalRequest().getMethod())
&& getState() == TERMINATED_STATE
&& transactionRequest.getMethod().equals
(Request.ACK)
&& requestOf != null) {
if (! this.getDialog().ackSeen) {
(this.getDialog()).ackReceived(
transactionRequest);
requestOf.processRequest
(transactionRequest, this);
}
} else if (
transactionRequest.getMethod().equals
(Request.CANCEL)) {
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION,
LogChannels.LC_JSR180,
"Too late to cancel Transaction");
}
}
// send OK and just ignore the CANCEL.
try {
this.sendMessage(transactionRequest.
createResponse(Response.OK));
} catch (IOException ex) {
// Transaction is already terminated
// just ignore the IOException.
}
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"Dropping request " + getState());
}
}
} catch (IOException e) {
raiseErrorEvent
(SIPTransactionErrorEvent.TRANSPORT_ERROR);
}
| public void | sendMessage(Message messageToSend)Sends a response message through this transactionand onto
the client.
// Message typecast as a response
Response transactionResponse;
// Status code of the response being sent to the client
int statusCode;
// Get the status code from the response
transactionResponse = (Response)messageToSend;
statusCode = transactionResponse.getStatusCode();
Dialog dialog = this.dialog;
// super.checkCancel(transactionResponse);
// Provided we have set the banch id for this we set the BID for the
// outgoing via.
if (this.getBranch() != null) {
transactionResponse.getTopmostVia().setBranch
(this.getBranch());
} else {
transactionResponse.getTopmostVia().removeParameter
(ViaHeader.BRANCH);
}
// Method of the response does not match the request used to
// create the transaction - transaction state does not change.
if (! transactionResponse.getCSeqHeader().getMethod().equals
(getOriginalRequest().getMethod())) {
sendSIPResponse(transactionResponse);
return;
}
if (this.dialog != null) {
if (this.dialog.getRemoteTag() == null &&
transactionResponse.getTo().getTag() != null &&
((SIPTransactionStack) this.getSIPStack()).isDialogCreated
(transactionResponse.getCSeqHeader().getMethod())) {
this.dialog.setRemoteTag(transactionResponse.getTo().getTag());
((SIPTransactionStack) this.getSIPStack())
.putDialog(this.dialog);
if (statusCode/100 == 1)
this.dialog.setState(Dialog.EARLY_STATE);
} else if (((SIPTransactionStack) this.getSIPStack())
.isDialogCreated
(transactionResponse.getCSeqHeader().getMethod())) {
if (statusCode / 100 == 2) {
if (!this.isInviteTransaction()) {
this.dialog.setState(Dialog.CONFIRMED_STATE);
} else {
if (this.dialog.getState() == -1)
this.dialog.setState(Dialog.EARLY_STATE);
}
} else if (statusCode >= 300 && statusCode <= 699 &&
(this.dialog.getState() == -1 ||
this.dialog.getState() == Dialog.EARLY_STATE)) {
this.dialog.setState(Dialog.TERMINATED_STATE);
}
} else if (transactionResponse.getCSeqHeader().getMethod()
.equals(Request.BYE) &&
statusCode/100 == 2) {
// Dialog will be terminated when the transction is terminated.
if (! isReliable()) this.dialog
.setState(Dialog.COMPLETED_STATE);
else this.dialog.setState(Dialog.TERMINATED_STATE);
}
}
// If the TU sends a provisional response while in the
// trying state,
if (getState() == TRYING_STATE) {
if (statusCode / 100 == 1) {
setState(PROCEEDING_STATE);
} else if (200 <= statusCode && statusCode <= 699) {
if (! isInviteTransaction()) {
setState(COMPLETED_STATE);
} else {
if (statusCode /100 == 2) {
this.collectionTime = TIMER_J;
setState(TERMINATED_STATE);
} else
setState(COMPLETED_STATE);
}
if (!isReliable()) {
enableRetransmissionTimer();
}
enableTimeoutTimer(TIMER_J);
}
// If the transaction is in the proceeding state,
} else if (getState() == PROCEEDING_STATE) {
if (isInviteTransaction()) {
// If the response is a failure message,
if (statusCode / 100 == 2) {
// Set up to catch returning ACKs
// Antonis Karydas: Suggestion
// Recall that the CANCEL's response will go
// through this transaction
// and this may well be it. Do NOT change the
// transaction state if this
// is a response for a CANCEL.
// Wait, instead for the 487 from TU.
if (!transactionResponse.getCSeqHeader().getMethod().equals
(Request.CANCEL)) {
setState(TERMINATED_STATE);
if (!isReliable()) {
((Dialog) this.getDialog())
.setRetransmissionTicks();
enableRetransmissionTimer();
}
this.collectionTime = TIMER_J;
enableTimeoutTimer(TIMER_J);
}
} else if (300 <= statusCode && statusCode <= 699) {
// Set up to catch returning ACKs
setState(COMPLETED_STATE);
if (!isReliable()) {
enableRetransmissionTimer();
}
// Changed to TIMER_H as suggested by
// Antonis Karydas
enableTimeoutTimer(TIMER_H);
// If the response is a success message,
} else if (statusCode / 100 == 2) {
// Terminate the transaction
setState(TERMINATED_STATE);
disableRetransmissionTimer();
disableTimeoutTimer();
}
// If the transaction is not an invite transaction
// and this is a final response,
} else if (200 <= statusCode && statusCode <= 699) {
// Set up to retransmit this response,
// or terminate the transaction
setState(COMPLETED_STATE);
if (!isReliable()) {
disableRetransmissionTimer();
enableTimeoutTimer(TIMER_J);
} else {
setState(TERMINATED_STATE);
}
}
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"SEND MESSAGE :: SERVER TRANSACTION STATE SET " +
getState());
}
// If the transaction has already completed,
} else if (getState() == COMPLETED_STATE) {
return;
}
try {
// Send the message to the client
lastResponse = transactionResponse;
sendSIPResponse(transactionResponse);
} catch (IOException e) {
setState(TERMINATED_STATE);
throw e;
}
| public void | sendResponse(Response response)Sends specified Response message to a Request which is identified by the
specified server transaction identifier. The semantics for various
application behaviour on sending Responses to Requests is outlined at
{@link SipListener#processRequest(RequestEvent)}.
Note that when a UAS core sends a 2xx response to an INVITE, the server
transaction is destroyed, by the underlying JAIN SIP implementation.
This means that when the ACK sent by the corresponding UAC arrives
at the UAS, there will be no matching server transaction for the ACK,
and based on this rule, the ACK is passed to the UAS application core,
where it is processed.
This ensures that the three way handsake of an INVITE that is managed by
the UAS application and not JAIN SIP.
try {
Dialog dialog = (Dialog) getDialog();
// Fix up the response if the dialog has already been established.
Response responseImpl = response;
int statusCode = responseImpl.getStatusCode();
int statusGroup = statusCode / 100;
if (statusGroup == 2 &&
parentStack.isDialogCreated
(responseImpl.getCSeqHeader().getMethod()) &&
dialog != null &&
dialog.getLocalTag() == null &&
responseImpl.getTo().getTag() == null) {
throw new SipException("ToHeader tag must be set for OK",
SipException.INVALID_MESSAGE);
}
if (statusGroup == 2 &&
responseImpl.getCSeqHeader().getMethod().equals
(Request.INVITE) &&
responseImpl.getHeader(Header.CONTACT) == null) {
throw new SipException("Contact Header is mandatory for the OK",
SipException.INVALID_MESSAGE);
}
// If sending the response within an established dialog, then
// set up the tags appropriately.
if (dialog != null && dialog.getLocalTag() != null) {
responseImpl.getTo().setTag(dialog.getLocalTag());
}
String fromTag = getRequest().getFromHeader().getTag();
// Backward compatibility slippery slope....
// Only set the from tag in the response when the
// incoming request has a from tag.
if (fromTag != null) {
responseImpl.getFromHeader().setTag(fromTag);
} else {
if (Logging.REPORT_LEVEL <= Logging.WARNING) {
Logging.report(Logging.WARNING, LogChannels.LC_JSR180,
"WARNING -- Null From tag Dialog layer in jeopardy!!");
}
}
sendMessage(response);
// Transaction successfully cancelled but dialog has not yet
// been established so delete the dialog.
if (Utils.equalsIgnoreCase(
responseImpl.getCSeqHeader().getMethod(),
Request.CANCEL)
&& statusGroup == 2
// && (!dialog.isReInvite())
&& parentStack.isDialogCreated(getOriginalRequest().getMethod())
&& (dialog.getState() == Dialog.INITIAL_STATE
|| dialog.getState() == Dialog.EARLY_STATE)) {
dialog.setState(Dialog.TERMINATED_STATE);
}
// See if the dialog needs to be inserted into the dialog table
// or if the state of the dialog needs to be changed.
if (dialog != null) {
dialog.printTags();
if (Utils.equalsIgnoreCase
(responseImpl.getCSeqHeader().getMethod(),
Request.BYE)) {
dialog.setState(Dialog.TERMINATED_STATE);
} else if (Utils.equalsIgnoreCase
(responseImpl.getCSeqHeader().getMethod(),
Request.CANCEL)) {
if (dialog.getState() == -1 ||
dialog.getState() == Dialog.EARLY_STATE) {
dialog.setState(Dialog.TERMINATED_STATE);
}
} else {
if (dialog.getLocalTag() == null &&
responseImpl.getTo().getTag() != null) {
if (statusCode != 100)
dialog.setLocalTag(responseImpl.getTo().getTag());
}
if (parentStack.isDialogCreated(responseImpl
.getCSeqHeader().getMethod())) {
if (statusGroup == 1 && statusCode != 100) {
dialog.setState(Dialog.EARLY_STATE);
} else if (statusGroup == 2) {
dialog.setState(Dialog.CONFIRMED_STATE);
}
// Enter into our dialog table provided this is a
// dialog creating method.
if (statusCode != 100)
parentStack.putDialog(dialog);
}
}
}
} catch (NullPointerException npe) {
if (Logging.REPORT_LEVEL <= Logging.ERROR) {
Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
"ServerTransaction.sendResponse(): NPE occured: " + npe);
npe.printStackTrace();
}
throw new SipException("NPE occured: " + npe.getMessage(),
SipException.GENERAL_ERROR);
}
| private void | sendSIPResponse(Response transactionResponse)Sends the SIP response.
if (transactionResponse.getTopmostVia().
getParameter(ViaHeader.RECEIVED) == null) {
// Send the response back on the same peer
// as received.
getMessageChannel().sendMessage(transactionResponse);
} else {
// Respond to the host name in the received parameter.
ViaHeader via = transactionResponse.getTopmostVia();
String host = via.getParameter(ViaHeader.RECEIVED);
int port = via.getPort();
if (port == -1) port = 5060;
String transport = via.getTransport();
Hop hop = new Hop(host+":"+port+"/" +transport);
MessageChannel messageChannel =
((SIPTransactionStack)getSIPStack()).
createRawMessageChannel(hop);
messageChannel.sendMessage(transactionResponse);
}
this.lastResponse = transactionResponse;
| public void | setOriginalRequest(Request originalRequest)Sets the original request.
super.setOriginalRequest(originalRequest);
// ACK Server Transaction is just a dummy transaction.
if (originalRequest.getMethod().equals("ACK"))
this.setState(TERMINATED_STATE);
| public void | setRequestInterface(SIPServerRequestInterface newRequestOf)Sets the real RequestInterface this transaction encapsulates.
requestOf = newRequestOf;
|
|