/*
* Portions Copyright 2000-2009 Sun Microsystems, Inc. All Rights
* Reserved. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package gov.nist.siplite.stack;
import gov.nist.siplite.message.*;
import gov.nist.siplite.header.*;
import gov.nist.siplite.address.*;
import gov.nist.core.*;
import gov.nist.siplite.*;
import java.io.IOException;
import javax.microedition.sip.SipException;
import com.sun.j2me.log.Logging;
import com.sun.j2me.log.LogChannels;
/**
* Represents a server transaction.
*
*
* @version JAIN-SIP-1.1
* <a href="{@docRoot}/uncopyright.html">This code is in the public domain.</a>
*/
public class ServerTransaction
extends Transaction
implements SIPServerRequestInterface {
/** Collection time. */
protected int collectionTime;
/** Real RequestInterface to pass messages to. */
private SIPServerRequestInterface requestOf;
/** Flag indicating this transaction is known to the stack. */
protected boolean isMapped;
/**
* Sends the SIP response.
* @param transactionResponse the transaction response
* @exception IOException if the response could not be sent
*/
private void sendSIPResponse(Response transactionResponse)
throws IOException {
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;
}
/**
* Delays the sending of the Trying state.
*/
class SendTrying extends Thread {
/** Current server transaction. */
ServerTransaction myTransaction;
/**
* Constructore with initial transaction.
* @param transaction the transaction to be sent
*/
public SendTrying(ServerTransaction transaction) {
myTransaction = transaction;
Thread myThread = new Thread(this);
myThread.start();
}
/** Main loop for sending transaction asynchronously. */
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException ex) { }
if (myTransaction.getState() == TRYING_STATE) {
try {
myTransaction.sendMessage
(myTransaction.getOriginalRequest().
createResponse(100, "Trying"));
} catch (IOException ex) {}
}
return;
}
}
/**
* Creates a new server transaction.
*
* @param newSIPMessageStack Transaction stack this transaction
* belongs to.
* @param newChannelToHeaderUse Channel to encapsulate.
*/
protected ServerTransaction(SIPTransactionStack newSIPMessageStack,
MessageChannel newChannelToHeaderUse) {
super(newSIPMessageStack, newChannelToHeaderUse);
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"Creating Server Transaction" + this);
// new Exception().printStackTrace();
}
}
/**
* Sets the real RequestInterface this transaction encapsulates.
*
* @param newRequestOf RequestInterface to send messages to.
*/
public void setRequestInterface(SIPServerRequestInterface newRequestOf) {
requestOf = newRequestOf;
}
/**
* Gets the processing infromation.
* @return the processing information
*/
public String getProcessingInfo() {
return requestOf.getProcessingInfo();
}
/**
* Deterines if the message is a part of this transaction.
*
* @param messageToHeaderTest Message to check if it is part of this
* transaction.
*
* @return True if the message is part of this transaction,
* false if not.
*/
public boolean isMessagePartOfTransaction(
Message messageToHeaderTest) {
// 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;
}
/**
* Sends out a trying response (only happens when the transaction is
* mapped). Otherwise the transaction is not known to the stack.
* @exception IOException if the attempt to send fails
*/
protected void map() throws IOException {
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;
}
}
}
/**
* Returns true if the transaction is known to stack.
* @return true if the transaction is already mapped
*/
public boolean isTransactionMapped() {
return this.isMapped;
}
/**
* 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.
*
* @param transactionRequest Request to process.
* @param sourceChannel Channel that received this message.
*/
public void processRequest(
Request transactionRequest,
MessageChannel sourceChannel)
throws SIPServerException {
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()
== SIPErrorCodes.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(SIPErrorCodes.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);
}
}
/**
* Sends a response message through this transactionand onto
* the client.
*
* @param messageToSend Response to process and send.
*/
public void sendMessage(Message messageToSend)
throws IOException {
// 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;
}
}
/**
* Gets the via host name.
* @return the via host
*/
public String getViaHost() {
return encapsulatedChannel.getViaHost();
}
/**
* Gets the via port number.
* @return the via port number
*/
public int getViaPort() {
return encapsulatedChannel.getViaPort();
}
/**
* Called by the transaction stack when a retransmission
* timer fires. This retransmits the last response when the
* retransmission filter is enabled.
*/
protected void fireRetransmissionTimer() {
try {
// Resend the last response sent by this transaction
if (isInviteTransaction() &&
((SIPTransactionStack)getSIPStack()).retransmissionFilter)
getMessageChannel().sendMessage(lastResponse);
} catch (IOException e) {
raiseErrorEvent
(SIPTransactionErrorEvent.TRANSPORT_ERROR);
if (lastResponse.getErrorListener() != null) {
lastResponse.getErrorListener().notifyError("Failed to " +
"retransmit the following response: " + lastResponse);
}
}
}
/**
* Called by the transaction stack when a timeout timer fires.
*/
protected void fireTimeoutTimer() {
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);
}
}
/**
* Gets the last response.
* @return the last response
*/
public Response getLastResponse() {
return this.lastResponse;
}
/**
* Sets the original request.
* @param originalRequest original request to remember
*/
public void setOriginalRequest(Request originalRequest) {
super.setOriginalRequest(originalRequest);
// ACK Server Transaction is just a dummy transaction.
if (originalRequest.getMethod().equals("ACK"))
this.setState(TERMINATED_STATE);
}
/**
* 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)}.
* <p>
* 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.
*
* @param response the Response to send to the Request
* @throws IOException if an I/O error occured
* @throws SipException if implementation cannot send response for any
* other reason
* @see Response
*/
public void sendResponse(Response response)
throws IOException, SipException {
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);
}
}
/**
* Returns this transaction.
* @return the response message channel
*/
public MessageChannel getResponseChannel() {
return this;
}
}