/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
* Copyright (c) Ericsson AB, 2004-2008. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.ericsson.ssa.sip.transaction;
import com.ericsson.ssa.container.reporter.Reporter;
import com.ericsson.ssa.container.sim.ApplicationDispatcher;
import com.ericsson.ssa.container.startup.SipMonitoring;
import com.ericsson.ssa.sip.DialogFragment;
import com.ericsson.ssa.sip.Dispatcher;
import com.ericsson.ssa.sip.SipServletRequestImpl;
import com.ericsson.ssa.sip.SipServletResponseImpl;
import com.ericsson.ssa.sip.dns.ResolverManager;
import com.ericsson.ssa.sip.timer.GeneralTimer;
import com.ericsson.ssa.sip.timer.GeneralTimerBase;
import static com.ericsson.ssa.sip.transaction.TransactionState.COMPLETED;
import static com.ericsson.ssa.sip.transaction.TransactionState.CONFIRMED;
import static com.ericsson.ssa.sip.transaction.TransactionState.PROCEEDING;
import static com.ericsson.ssa.sip.transaction.TransactionState.TERMINATED;
import static com.ericsson.ssa.sip.transaction.TransactionTimer.TimerRemoveTransaction;
import static com.ericsson.ssa.sip.transaction.TransactionTimer.TimerG;
import static com.ericsson.ssa.sip.transaction.TransactionTimer.TimerH;
import static com.ericsson.ssa.sip.transaction.TransactionTimer.TimerI;
import java.io.UnsupportedEncodingException;
import java.util.logging.Level;
/**
* @author ekrigro TODO To change the template for this generated type comment
* go to Window - Preferences - Java - Code Style - Code Templates
*/
public class InviteServerTransaction extends ServerTransaction {
// T1 ->T1*2->
private GeneralTimer _timerG;
// 64*T1
private GeneralTimer _timerH;
// T4 for UDP 0s TCP
private GeneralTimer _timerI;
// Invite re-transmission timer for UDP
private GeneralTimer _timerRemoveTransaction;
// cancel trigger response will only be set
// when response status range is 100 to 199
private Object _cancelLock = new Object();
private Object _cancelTriggerLock = new Object();
private SipServletResponseImpl _cancelTriggerResponse = null;
private SipServletRequestImpl _cancel = null;
/**
* @param state
* @param req
*/
public InviteServerTransaction(String id, SipServletRequestImpl req) {
super(id, PROCEEDING, req);
// Generate 100 if more than 200ms...
send100Trying(req);
}
private void send100Trying(SipServletRequestImpl req) {
_response = req.create100TryingResponse();
try {
_response.serializeForTransmission();
} catch (UnsupportedEncodingException e) {
// If SipFactoryImpl.SIP_CHARSET = "UTF-8", no exception should
// occur since this charset is mandatory to exist.
}
_response.popDispatcher().dispatch(_response);
}
synchronized boolean handle(SipServletRequestImpl req) {
if (req.getMethod().equals("ACK") && (_state == COMPLETED)) {
toConfirmed();
} else if ((_response != null) &&
((_state == PROCEEDING) || (_state == COMPLETED))) {
_response.restoreRetransmissionTransactionStack();
Dispatcher d = _response.popDispatcher();
// it's acceptable to block the transaction
// during the response transmission since
// it's an answer to a request re-transmission
if (d != null) {
d.dispatch(_response);
}
if (SipMonitoring.isEnabled(SipMonitoring.TRANSACTION_MANAGER)) {
updateLastAccessTimestamp();
}
} else if (req.getMethod().equals("INVITE") && (_state == TERMINATED)){
if (_log.isLoggable(Level.FINE)){
_log.log(Level.FINE, "Received a re-transmitted INVITE ");
}
}
return false; // Should not continue
}
// Record the last response for lost retransmissions
public void dispatch(SipServletResponseImpl resp) {
// a 100 Trying should never be forwarded...
synchronized (this) {
if (resp.getStatus() == 100) {
if (_state == PROCEEDING) {
handleCancelTrigger(resp);
}
return;
}
int status = resp.getStatus() / 100;
switch (_state) {
case PROCEEDING:
_response = resp;
try {
resp.serializeForTransmission();
} catch (UnsupportedEncodingException e) {
// If SipFactoryImpl.SIP_CHARSET = "UTF-8", no exception
// should
// occur since it is mandatory for this charset to exist.
}
if (status == 1) {
handleCancelTrigger(resp);
break;
} else if (status == 2) {
if (!_reliableTransport &&
TransactionManager.isQuenchUDPRetransmission()) {
/* Takes care of race conditions during UDP retransmissions
* The UAC can retransmit an INVITE if it has not
* got the 100. But if the retransmitted INVITE
* reaches the server after the server transaction
* has ended, then it would create a new transaction.
* Only the transaction is kept alive for 64*T1.
* The request and response objects are cleaned up.
*/
terminate(false);
clearRequest();
_response = null;
_timerRemoveTransaction =
_timerService.createTimer(this, 64 * T1,
TimerRemoveTransaction);
if (SipMonitoring.isEnabled(SipMonitoring.TRANSACTION_MANAGER)) {
long transactionTime = System.currentTimeMillis() -
getTransactionStartTime();
_transactionManager.recordTransactionTime(transactionTime);
if (_log.isLoggable(Level.FINER)) {
_log.log(Level.FINER, "TransactionTime:" + transactionTime);
}
}
} else {
terminate();
}
} else {
toCompleted(); // 3xx to 6xx
}
break;
case COMPLETED:
return;
case CONFIRMED:
return;
case TERMINATED:
if (status == 2) {
// RFC3261, 16.7 bullit 9 If the server transaction is
// no longer available to handle the transmission, the element
// MUST forward the response statelessly by sending it to the
// server transport.
break;
}
return; // Should not get any response from TU in this state
default:
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE, "IllegalState in ICT = " + _state);
}
}
}
Dispatcher d = resp.popDispatcher();
try {
if (d != null) {
d.dispatch(resp);
}
} // Transport Error
catch (Exception e) {
if (_state != TERMINATED) {
terminate();
}
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE, "Handled : ", e);
}
}
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.TimerListener#timeout(javax.servlet.sip.ServletTimer)
*/
public void timeout(GeneralTimer timer) {
TransactionTimer tt = (TransactionTimer) timer.getInfo();
switch (tt) {
case TimerG:
synchronized (this) {
if (_state == COMPLETED) {
// Have to dirty cast in order not to reimplement hole
// structure
long delay = ((GeneralTimerBase) timer).getDelay();
// calculate next timer*2 but less then T2 (4sec)
delay = ((delay * 2) <= T2) ? (delay * 2) : T2;
// schedule new timer
_timerG = _timerService.createTimer(this, delay, TimerG);
// resend the response
_response.restoreRetransmissionTransactionStack();
Dispatcher d = _response.popDispatcher();
// it's acceptable to block the transaction
// during the response transmission
if (d != null) {
d.dispatch(_response);
}
if (SipMonitoring.isEnabled(
SipMonitoring.TRANSACTION_MANAGER)) {
updateLastAccessTimestamp();
}
}
}
break;
case TimerH:
terminate();
break; // Time to do some GC
case TimerI:
terminate();
break;
case TimerRemoveTransaction:
_transactionManager.remove(this);
if (_timerRemoveTransaction != null) {
_timerRemoveTransaction.cancel();
_timerRemoveTransaction = null;
}
break;
default:
_log.log(Level.FINE, "IllegalTimer in ICT = " + tt);
}
}
private void toCompleted() {
stopCancel();
_timerH = _timerService.createTimer(this, 64 * T1, TimerH);
_state = COMPLETED;
if (!_reliableTransport) {
_timerG = _timerService.createTimer(this, T1, TimerG);
}
}
private void toConfirmed() {
if (_timerH != null) {
_timerH.cancel();
_timerH = null;
}
if (!_reliableTransport) { // Start timer I
if (SipMonitoring.isEnabled(SipMonitoring.TRANSACTION_MANAGER)) {
updateLastAccessTimestamp();
}
if (_timerG != null) {
_timerG.cancel();
_timerG = null;
}
_state = CONFIRMED;
_timerI = _timerService.createTimer(this, T4, TimerI);
} else {
_state = TERMINATED; // TimerI = 0
super.terminate();
}
}
protected synchronized void terminate() {
terminate(true);
}
/* TODO check synchronization */
protected synchronized void terminate(boolean removetransaction) {
if (removetransaction) {
super.terminate();
}
stopCancel();
if (_timerG != null) {
_timerG.cancel();
_timerG = null;
}
if (_timerH != null) {
_timerH.cancel();
_timerH = null;
}
if (_timerI != null) {
_timerI.cancel();
_timerI = null;
}
_state = TERMINATED;
}
private SipServletResponseImpl getPendingCancelTrigger() {
synchronized (_cancelTriggerLock) {
return _cancelTriggerResponse;
}
}
private void setPendingCancelTrigger(SipServletResponseImpl resp) {
synchronized (_cancelTriggerLock) {
if (_cancelTriggerResponse == null) {
_cancelTriggerResponse = resp;
}
}
}
private SipServletRequestImpl getPendingCancel() {
synchronized (_cancelLock) {
return _cancel;
}
}
private void setPendingCancel(SipServletRequestImpl cancel) {
if (_cancel == null) {
synchronized (_cancelLock) {
if (_cancel == null) {
_cancel = cancel;
}
}
}
}
private void stopCancel() {
synchronized (_cancelTriggerLock) {
_cancelTriggerResponse = null;
}
synchronized (_cancelLock) {
_cancel = null;
}
}
private void handleCancel(SipServletRequestImpl cancel,
SipServletResponseImpl cancelPendingTrigger) {
// must get next hop for cancel from incoming response...
if ((cancel != null) && (cancelPendingTrigger != null)) {
DialogFragment dialog = getRequest().getDialog();
if (dialog != null) {
Dispatcher next = dialog.getFirst();
if (next != null) {
// Servlet reporter should not normally be intercepted in transaction layer
// but we need to do it for cancel since cancel bypasses the layers and goes straigth into
// first pathnode!
Reporter reporter = ApplicationDispatcher.getInstance().getServletReporters();
if (reporter != null) {
String interceptedAt;
if (cancelPendingTrigger.getApplicationSessionImpl() == null) {
interceptedAt = this.getClass().getSimpleName();
} else {
interceptedAt = cancelPendingTrigger.getApplicationSessionImpl().getApplicationName() + "/"
+ cancelPendingTrigger.getSessionImpl().getHandler();
}
reporter.logIncomingRequest(Reporter.InterceptionType.SERVLET, cancel, interceptedAt);
}
// lets go directly to path node...
// this is unique in that the dialog we should lock on is not the dialog of the cancel
// but the one of the original request...
// the dialog of the cancel is not set...
dispatchInUOW(next, cancel, dialog);
}
} else {
// This should only happen for "statelessly" proxied
// requests.... or To be more accurate: sailfin is always transaction
// stateful but may be dialog stateless in case no application matched
// the invite request
if (cancelPendingTrigger.getCancelVia() != null) {
// create new cancel from the Invite
SipServletRequestImpl newCancel = getRequest().createCancelImpl();
// lets answer incoming cancel with 200 OK
SipServletResponseImpl resp = cancel.createTerminatingResponse(200);
resp.setRemote(cancel.getRemote());
resp.popDispatcher().dispatch(resp);
// forward the Cancel
// assign via from saved top via of provisional response
// (i.e. the cancelTrigger)
// resolve it and send it out on the network
newCancel.setHeader(cancelPendingTrigger.getCancelVia());
newCancel.pushApplicationDispatcher(ResolverManager.getInstance());
newCancel.popDispatcher().dispatch(newCancel);
} else {
// This should never happen since InviteST protection
// mechanism is now fixed and back in place
if (_log.isLoggable(Level.WARNING)) {
_log.log(Level.WARNING, "dialog reference is null in:" + cancel);
}
}
}
}
}
private void handleCancelTrigger(SipServletResponseImpl pendingTrigger) {
SipServletRequestImpl cancel = getPendingCancel();
if (cancel != null) {
handleCancel(cancel, pendingTrigger);
} else {
// save cancel trigger response to enable cancel
// forwarding later if cancel request arrives...
setPendingCancelTrigger(pendingTrigger);
}
}
void handleCancel(ServerTransaction st) {
SipServletRequestImpl cancel = st.getRequest();
if (_state != PROCEEDING) {
SipServletResponseImpl resp = cancel.createTerminatingResponse(481);
resp.setRemote(cancel.getRemote());
resp.popDispatcher().dispatch(resp);
return;
}
// is this a cancel on a re-invite?
if (getRequest().isInitial() == false && getRequest().getDialog() != null) {
// In case of cancel on re-invite we might immediately act on the
// cancel without waiting for the provisional response
DialogFragment dialog = getRequest().getDialog();
Dispatcher firstPathNode = dialog.getFirst();
if (firstPathNode == null) {
throw new IllegalStateException("Dialog reference for cancel on re-invite does not contain a pathnode");
}
dispatchInUOW(firstPathNode, cancel, dialog);
return;
}
SipServletResponseImpl cancelPendingTrigger = getPendingCancelTrigger();
if (cancelPendingTrigger != null) {
handleCancel(st.getRequest(), cancelPendingTrigger);
} else {
// save cancel request to enable cancel forwarding
// later when cancel trigger response arrives...
setPendingCancel(cancel);
}
}
}