/*
* 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;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.logging.Level;
// inserted by hockey (automatic)
import java.util.logging.Logger;
import org.jvnet.glassfish.comms.util.LogUtil;
import javax.servlet.Servlet;
import javax.servlet.sip.ServletParseException;
import javax.servlet.sip.SipServletMessage;
import javax.servlet.sip.SipServletRequest;
import javax.servlet.sip.UAMode;
/**
* This is a Singleton handling all SUBSCRIBE, REFER and NOTIFY messages and the
* associated responses. Each unique event header with the id parameter and
* value will be stored in this dialog. When a NOTIFY terminating the
* SUBSCRIBE/REFER session arrives the session will be removed from this dialog.
*
* @author ehsroha
*/
public class SUBSCRIBE_REFERSession extends FSM {
private static final Logger m_Log = LogUtil.SIP_LOGGER.getLogger();
private static SUBSCRIBE_REFERSession m_Instance = new SUBSCRIBE_REFERSession();
private SUBSCRIBE_REFERSession() {
}
/**
* Will return a SUBSCRIBE_REFERSession if the message matches this FSM.
*
* @param req
* the incoming message is the key to decide which FSM to return
* @return a GeneralSession or null if no match occured
*/
public static FSM createFSM(SipServletMessage m) {
if (isStaticResponsible(m)) {
return m_Instance;
}
return null;
}
public Object clone() {
return m_Instance;
}
/**
* Returns whether the message contains a Subscription-State header with
* condition terminated or not.
*
* @param m
* the messge holding the Subscription-State header
* @return whether a termination condition was found or not
*/
private static boolean isNotifyTerminatedState(SipServletMessage m) {
// Example "Subscription-State: terminated"
String subState = m.getHeader("Subscription-State");
if (subState == null) {
return false;
}
return subState.matches("terminated");
}
protected static boolean hasEventHeader(SipServletMessage m) {
return m.getHeader(Header.EVENT) != null;
}
protected static boolean checkReferToHeader(SipServletMessage m) {
ListIterator referTo = m.getHeaders(Header.REFER_TO);
ListIterator referToShort = m.getHeaders(Header.REFER_TO_SHORT);
boolean referToCriterion = false;
try {
if ((referTo.next() != null) && !referTo.hasNext()) {
referToCriterion = true;
}
} catch (NoSuchElementException e) {
}
try {
if ((referToShort.next() != null) && !referToShort.hasNext()) {
referToCriterion = true;
}
} catch (NoSuchElementException e) {
}
return referToCriterion;
}
/**
* Extracts the Event header and returns the event and the id number as a
* combined key. Example 1. Event: foo; param = yuy; id=25; expires=3600 will
* return the combined key: foo25, Example 2. Event: refer;id=256565858 will
* return the combined key: refer256565858
*
* @param m
* the messge holding the Event header
* @return the combined key value or null if error or Event header was not
* found
*/
protected static String getEventType(SipServletMessage m) {
// Example "Event: foo; param=abcd; id=25"
// Example "Event: refer; param=abcd; id=256565858 (id equal to CSeq of
// REFER)"
// TODO: replace the ' ' with proper isWhiteSpace algorithm according to
// RFC
// 3261 (BNF notation)
String event = m.getHeader(Header.EVENT);
if (event == null) {
return null;
}
int posStart = 0;
// trim
while ((event.charAt(posStart) == ' ') && (posStart < event.length())) {
++posStart;
}
int posStop = posStart;
// find first ';'
while ((posStop < event.length()) && (event.charAt(posStop) != ';')) {
++posStop;
}
String eventType = null;
if (posStop < event.length()) {
eventType = event.substring(posStart, posStop);
} else {
// didn't find ';' only event without id parameter
return event.substring(posStart, posStop);
}
posStart = posStop + 1;
// find id parameter
// TODO should ID be supported or only id
while ((posStart < event.length()) &&
(((event.charAt(posStart) != 'i') &&
(event.charAt(posStart) != 'I')) ||
((event.charAt(posStart + 1) != 'd') &&
(event.charAt(posStart + 1) != 'D')))) {
++posStart;
}
// lets make sure that the characters before id are OK
int tmpPos = posStart - 1;
while (event.charAt(tmpPos) == ' ') {
--tmpPos;
}
if (event.charAt(tmpPos) != ';') {
return eventType;
}
posStart = posStart + 2;
// trim
while ((event.charAt(posStart) == ' ') && (posStart < event.length())) {
++posStart;
}
// find id number after '='
if ((posStart < event.length()) && (event.charAt(posStart) == '=')) {
posStop = posStart++;
// trim
while ((posStop < event.length()) &&
(event.charAt(posStop) != ' ') &&
(event.charAt(posStop) != ';')) {
++posStop;
}
return eventType + event.substring(posStart, posStop);
}
return eventType;
}
/**
* This FSM is responsible if the message is of method type: SUBSCRIBE, REFER
* or NOTIFY.
*
* @param m
* the incoming message is the key to decide whether this FSM will
* match or not.
*/
private static boolean isStaticResponsible(SipServletMessage m) {
// SUBSCRIBE
// Event: foo; id=25
// REFER
// CSeq: 11952389 REFER
// NOTIFY
// Event: refer; id=11952389 (NOTIFY to REFER)
// Event: foo; id=25 (NOTIFY to SUBSCRIBE)
// Subscription-State: terminated
String method = m.getMethod();
return method.equals("SUBSCRIBE") || method.equals("REFER") ||
method.equals("NOTIFY");
}
public boolean isResponsible(SipServletMessage m) {
return isStaticResponsible(m);
}
private void setDerivedOrOriginalSession(SipServletResponseImpl resp, UA ua) {
// if the ua has a session with same to tag as the dialog, use it...
if ((ua.getSipSession() != null) && !ua.getSipSession().hasNoToTag() &&
(resp.getDialog().getToTag() != null) &&
resp.getDialog().getToTag().equals(ua.getSipSession().getToTag())) {
resp.setSession(ua.getSipSession());
} else {
// ...otherwise fetch or create one
DialogFragment df = resp.getDialog();
// lets update to-tag of dialog...
SipSessionBase s = resp.getSessionImpl()
.getOriginalOrDerivedSessionAndRegisterDialog(resp,
df);
if (s.isDerived()) {
// lets set the session cuz it is a derived session
resp.setSession(s);
}
ua.setSipSession(s);
}
}
private void doResponseUAC(SipServletResponseImpl resp, UA ua) {
if (resp.hasToTag()) {
String eventType = null;
String method = resp.getMethod();
if (method.equals("REFER")) {
setDerivedOrOriginalSession(resp, ua);
if (resp.getRequest().isInitial()) {
try {
ua.saveRouteSetRemoteTarget(resp);
} catch (ServletParseException e) {
throw new IllegalStateException(
"Parse problem of Contact or Record-Route");
}
}
eventType = "refer" + resp.getCSeqNumber();
ua.addDialogSession(method, eventType);
} else {
// need to get Event header from request of response
// because there is no Event header in response
SipServletRequest req = resp.getRequest();
eventType = getEventType(req);
if (eventType != null) {
if (method.equals("SUBSCRIBE")) {
setDerivedOrOriginalSession(resp, ua);
if (resp.getRequest().isInitial()) {
try {
ua.saveRouteSetRemoteTarget(resp);
} catch (ServletParseException e) {
throw new IllegalStateException(
"Parse problem of Contact or Record-Route");
}
}
ua.addDialogSession(method, eventType);
} else if (method.equals("NOTIFY") &&
isNotifyTerminatedState(req)) {
// TODO: this doesn't work if NOTIFY didn't use
// the CSeq number from REFER as id value
ua.removeDialogSession(method, eventType);
}
}
}
}
}
private void doResponseUAS(SipServletResponseImpl resp, UA ua) {
String eventType = null;
String method = resp.getMethod();
if (method.equals("REFER")) {
if (!resp.hasToTag()) {
resp.createTag(Header.TO);
setDerivedOrOriginalSession(resp, ua);
// make sure that getLocalParty() and getRemoteParty() change
// behaviour
SipSessionDialogImpl s = (SipSessionDialogImpl) resp.getSessionImpl();
s.swapLocalRemote();
try {
// need to save system headers
ua.saveContactRouteSetRemoteTarget(resp);
} catch (ServletParseException e) {
throw new IllegalStateException(
"Parse problem of Contact or Record-Route");
}
eventType = "refer" + resp.getCSeqNumber();
ua.addDialogSession(method, eventType);
}
} else {
// need to get Event header from request of response
// because there is no Event header in response
SipServletRequest req = resp.getRequest();
eventType = getEventType(req);
int status = resp.getStatus();
if (eventType != null) {
if (method.equals("SUBSCRIBE")) {
if (!resp.hasToTag()) {
resp.createTag(Header.TO);
if ((status >= 200) && (status < 300)) {
setDerivedOrOriginalSession(resp, ua);
// make sure that getLocalParty() and getRemoteParty() change
// behaviour
SipSessionDialogImpl s = (SipSessionDialogImpl) resp.getSessionImpl();
s.swapLocalRemote();
try {
// need to save system headers
ua.saveContactRouteSetRemoteTarget(resp);
} catch (ServletParseException e) {
throw new IllegalStateException(
"Parse problem of Contact or Record-Route");
}
ua.addDialogSession(method, eventType);
}
}
} else if (method.equals("NOTIFY") &&
isNotifyTerminatedState(req)) {
// TODO: this doesn't work if NOTIFY didn't use
// the CSeq number from REFER as id value
ua.removeDialogSession(method, eventType);
}
} else {
if ((status >= 200) && (status < 300)) {
// RFC3265 mandates Event header for SUBSCRIBE and NOTIFY request
throw new IllegalStateException(
"Original request missing Event header field");
}
}
}
}
public void send(SipServletRequestImpl req, UA ua)
throws IllegalStateException {
if (m_Log.isLoggable(Level.FINEST)) {
m_Log.log(Level.FINEST, req.toDebugString());
}
String method = req.getMethod();
if (method.equals("SUBSCRIBE") || method.equals("NOTIFY")) {
// RFC3265 mandates Event header for SUBSCRIBE and NOTIFY request
if (!hasEventHeader(req)) {
throw new IllegalStateException("Missing Event header field");
}
} else if (method.equals("REFER")) {
// RFC3515 mandates Refer-To for REFER request.
if (!checkReferToHeader(req)) {
throw new IllegalStateException("Missing Refer-To header field");
}
}
// the request need to be cloned before sending
SipServletRequestImpl clone = (SipServletRequestImpl) req.clone();
clone.setTransactionRequest(req);
// lets run in new thread...
super.send(clone, ua);
}
public void send(SipServletResponseImpl resp, UA ua)
throws IllegalStateException {
if (m_Log.isLoggable(Level.FINEST)) {
m_Log.log(Level.FINEST, resp.toDebugString());
}
doResponseUAS(resp, ua);
resp.getSessionImpl().updateSipSessionState(resp, ua.getType());
if (!resp.isCommitted() && resp.getSessionImpl().isB2buaHelper()) {
resp.getSessionImpl().addPendingMessage(resp, UAMode.UAS);
}
// the response need to be cloned before sending
final SipServletResponseImpl clone = (SipServletResponseImpl) resp.clone();
// if there is a transaction request, added it to the response...
SipServletRequestImpl req = resp.getRequestImpl().getTransactionRequest();
if (req != null) {
clone.setRequest(req);
clone.setSession(req.getSessionImpl());
}
IWRHandler.getInstance().handle(null, resp);
// lets run in new thread...
super.send(clone, ua);
}
private void setDerivedOrOriginalSession(SipServletRequestImpl req, UA ua)
throws ServletParseException {
// dialog creational NOTIFY
if (req.getMethod().equals("NOTIFY")) {
// if the context has a session with same to tag as the dialog, use
// it...
if (!ua.getSipSession().hasNoToTag() &&
(req.getDialog().getToTag() != null) &&
req.getDialog().getToTag()
.equals(ua.getSipSession().getToTag())) {
req.setSession(ua.getSipSession());
} else {
// ...otherwise fetch or create one
DialogFragment df = req.getDialog();
// lets update to-tag of dialog...
SipSessionBase s = ua.getSipSession()
.getOriginalOrDerivedSessionAndRegisterDialog(req,
df);
// lets set the session
req.setSession(s);
// update ua
ua.setSipSession(s);
// need to save system headers
ua.saveContactRouteSetRemoteTarget(req);
}
} else {
// the response should already have created
// the dialog for all other methods...
req.setSession(ua.getSipSession());
}
}
public void dispatch(SipServletRequestImpl req, UA ua) {
if (m_Log.isLoggable(Level.FINEST)) {
m_Log.log(Level.FINEST, req.toDebugString());
}
String method = req.getMethod();
if (method.equals("SUBSCRIBE") || method.equals("NOTIFY")) {
if (!hasEventHeader(req)) {
// RFC3265 mandates Event header for SUBSCRIBE and NOTIFY request
SipServletResponseImpl resp = req.createTerminatingResponse(400,
"Missing Event header field");
resp.popDispatcher().dispatch(resp);
return;
}
} else if (method.equals("REFER")) {
if (!checkReferToHeader(req)) {
// RFC 3515 Refer Method. One Refer-To header is mandatory.
SipServletResponseImpl resp = req.createTerminatingResponse(400,
"Missing Refer-To header field");
resp.popDispatcher().dispatch(resp);
return;
}
}
try {
setDerivedOrOriginalSession(req, ua);
Servlet s = ua.getServlet(req.getSessionImpl().getHandler());
if (s != null) {
req.getSessionImpl().updateSipSessionState(req, ua.getType());
if (!req.isCommitted() && req.getSessionImpl().isB2buaHelper()) {
req.getSessionImpl().addPendingMessage(req, UAMode.UAS);
}
s.service(req, null);
} else {
if (m_Log.isLoggable(Level.INFO)) {
m_Log.log(Level.INFO,
"Could not find servlet name: " +
req.getSessionImpl().getHandler() +
" in application: " +
req.getSessionImpl().getApplicationSessionImpl()
.getName());
}
}
} catch (Exception e) {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE, "Caught Exception: ", e);
}
// problem in servlet...
// TR HH52078
SipServletResponseImpl resp = req.createTerminatingResponse(500);
if (resp != null) {
resp.popDispatcher().dispatch(resp);
}
// also remove session
String eventType = getEventType(req);
if (eventType != null) {
// TODO must adjust
ua.removeDialogSession(req.getMethod(), eventType);
}
}
}
public void dispatch(SipServletResponseImpl resp, UA ua) {
if (m_Log.isLoggable(Level.FINEST)) {
m_Log.log(Level.FINEST, resp.toDebugString());
}
doResponseUAC(resp, ua);
try {
Servlet s = ua.getServlet(resp.getSessionImpl().getHandler());
if (s != null) {
resp.getSessionImpl().updateSipSessionState(resp, ua.getType());
resp.setIncoming();
// first message will create the pending
// message lists for UAC and UAS..
if (resp.getRequestImpl().isB2buaHelper()) {
resp.getSessionImpl().createPendingMessageHelper();
}
// ...if pending lists exist, add pending uncommitted message
if (!resp.isCommitted() && resp.getSessionImpl().isB2buaHelper()) {
resp.getSessionImpl().addPendingMessage(resp, UAMode.UAC);
}
s.service(null, resp);
} else {
if (m_Log.isLoggable(Level.INFO)) {
m_Log.log(Level.INFO,
"Could not find servlet name: " +
resp.getSessionImpl().getHandler() +
" in application: " +
resp.getSessionImpl().getApplicationSessionImpl()
.getName());
}
}
} catch (Exception e) {
// problem in servlet, lets drop response
if (m_Log.isLoggable(Level.INFO)) {
m_Log.log(Level.INFO, "Problem in servlet.", e);
}
}
}
/**
* Will always return false since it's a Singleton class
*/
public boolean isDeletable() {
return false;
}
}