/*
* 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 com.ericsson.ssa.container.sim.ApplicationDispatcher;
import com.ericsson.ssa.container.sim.ServletDispatcher;
import com.ericsson.ssa.sip.PathNode.Type;
import com.ericsson.ssa.sip.dns.TargetResolver;
import org.jvnet.glassfish.comms.deployment.backend.SipApplication;
import org.jvnet.glassfish.comms.util.LogUtil;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.Servlet;
import javax.servlet.sip.Address;
import javax.servlet.sip.Proxy;
import javax.servlet.sip.ProxyBranch;
import javax.servlet.sip.ServletParseException;
import javax.servlet.sip.SipServletRequest;
import javax.servlet.sip.SipURI;
import javax.servlet.sip.URI;
/**
* The Proxy implementation.
*
* @author ehsroha
*/
public class ProxyImpl extends AbstractProxyImpl {
private static final Logger _log = LogUtil.SIP_LOGGER.getLogger();
// feature of JSR 289 10.2.3 Sending Responses
private static final URI _virtualProxyBranchURI = new SipURIImpl(false,
"proxy_uas", "localhost");
private SipServletRequestImpl _originalRequest = null;
// FIXME maybe we should rely on Timer C in the transaction instead?
// use timer C as default
private int _proxyTimeout = ProxyBranchImpl.TimerC;
// set the first 2xx response received or the best
// 3xx-6xx response that has been forwarded
private SipServletResponseImpl _bestResponse = null;
private boolean _is6xx = false;
private final Collection<TargetSet> _targetSets = new ConcurrentLinkedQueue<TargetSet>();
private CreatedTargetSet _createdSet = new CreatedTargetSet(this);
private boolean _isFirst = true;
private ProxyBranchImpl _virtualProxyBranch = null;
private boolean _noCancel = false;
boolean _isRecordRoute = false;
private ProxyState _proxyState = ProxyState.NO_BEST_RESPONSE;
/**
* The constructor of the Proxy implementation.
*
* @param session
* the SipSession associated with this proxy.
* @param originalRequest
* the request received from the upstream caller.
*/
public ProxyImpl(SipApplicationSessionImpl appSession,
SipServletRequestImpl originalRequest) {
super(appSession.getName());
_originalRequest = originalRequest;
SipSessionBase s = originalRequest.getSessionImpl();
s.setType(getType());
if (_log.isLoggable(Level.FINEST)) {
_log.log(Level.FINEST, "ProxyImpl reference = " + this);
}
}
/**
* Acquire the state BEST_RESPONSE_CANDIDATE so that the servlet can be invoked.
*
* @return Whether it was possible to aquire the best response candidate state.
*/
private synchronized boolean acquireBestResponseCandidateState() {
if (_proxyState == ProxyState.NO_BEST_RESPONSE) {
_proxyState = ProxyState.BEST_RESPONSE_CANDIDATE;
return true;
}
return false;
}
/**
* After doing the aquisition and servlet invocation, reset
* the state to NO_BEST_RESPONSE unless there is
* confirmed best response.
*
*/
private synchronized void resetReponseCandidateAcquisition() {
if (_proxyState != ProxyState.BEST_RESPONSE) {
_proxyState = ProxyState.NO_BEST_RESPONSE;
}
}
/**
* Confirm that there is a best response.
*
*/
private synchronized void confirmBestResponse() {
_proxyState = ProxyState.BEST_RESPONSE;
}
public Type getType() {
return Type.Proxy;
}
public Collection<TargetSet> getTargetSets() {
return _targetSets;
}
public void dispatch(SipServletRequestImpl req, ProxyContext pc) {
if (_log.isLoggable(Level.FINEST)) {
_log.log(Level.FINEST, req.toDebugString());
}
// update max forwards
int maxForwards = req.getMaxForwards();
if (maxForwards == 0) {
if (_log.isLoggable(Level.INFO)) {
_log.log(Level.INFO, "Too Many Hops for request = " + req);
}
SipServletResponseImpl response = req.createTerminatingResponse(483);
if (response != null) {
response.popDispatcher().dispatch(response);
}
return;
} else if (maxForwards == -1) {
maxForwards = 70;
} else {
maxForwards--;
}
req.setMaxForwards(maxForwards);
if (req.getMethod().equals("CANCEL")) {
if (!getTargetSets().isEmpty()) {
cancelIntern(req.getRawHeader(Header.REASON));
} else {
// no branch found, the initial request has
// not been proxied jet, respond with 487
synchronized (this) {
if (_bestResponse == null) {
// setting best response will stop further
// proxyTo/startProxy calls
_bestResponse = getOriginalRequestImpl()
.createTerminatingResponse(487);
}
}
if (_bestResponse != null) {
_bestResponse.popDispatcher().dispatch(_bestResponse);
}
}
// lets answer cancel with 200 OK...
SipServletResponseImpl resp = req.createTerminatingResponse(200);
resp.setRemote(req.getRemote());
resp.popDispatcher().dispatch(resp);
try {
// notify the servlet about the CANCEL...
setDerivedOrOriginalSession(req, pc);
Servlet s = getServlet(pc.getSipSession().getHandler());
if (s != null) {
req.setProxyContext(pc);
req.getSessionImpl().updateSipSessionState(req, getType());
s.service(req, null);
} else {
if (_log.isLoggable(Level.INFO)) {
_log.log(Level.INFO,
"Could not find servlet name: " +
req.getSessionImpl().getHandler() +
" in application: " +
req.getSessionImpl().getApplicationSessionImpl()
.getName());
}
}
} catch (Exception e) {
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE, "Problem in servlet ", e);
}
// problem in servlet, setting best response
// will stop further proxyTo/startProxy calls
if (_bestResponse == null) {
synchronized (this) {
if (_bestResponse == null) {
_bestResponse = getOriginalRequestImpl()
.createTerminatingResponse(500);
}
}
if (_bestResponse != null) {
_bestResponse.popDispatcher().dispatch(_bestResponse);
}
}
}
} else {
if (shouldRecordRoute()) {
try {
// support for dialog creational NOTIFY
setDerivedOrOriginalSession(req, pc);
// if record route is enabled invoke the servlet
Servlet s = getServlet(pc.getSipSession().getHandler());
if (s != null) {
req.setProxyContext(pc);
req.getSessionImpl()
.updateSipSessionState(req, getType());
s.service(req, null);
if (req.getSupervised()) {
// only if the response will visit this proxy
// the request need to be cloned
SipServletRequestImpl clone = (SipServletRequestImpl) req.clone();
clone.pushTransactionDispatcher(pc);
clone.setTransactionRequest(req);
clone.popDispatcher().dispatch(clone);
} else {
req.popDispatcher().dispatch(req);
}
} else {
if (_log.isLoggable(Level.INFO)) {
_log.log(Level.INFO,
"Could not find servlet name: " +
req.getSessionImpl().getHandler() +
" in application: " +
req.getSessionImpl().getApplicationSessionImpl()
.getName());
}
}
} catch (Exception e) {
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE, "Problem in servlet ", e);
}
// problem in servlet, setting best response
// will stop further proxyTo calls
synchronized (this) {
_bestResponse = req.createTerminatingResponse(500);
}
if (_bestResponse != null) {
_bestResponse.popDispatcher().dispatch(_bestResponse);
}
}
} else {
req.popDispatcher().dispatch(req);
}
}
}
private void dispatchAndClone(SipServletResponseImpl resp) {
// the request need to be cloned
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());
}
clone.popDispatcher().dispatch(clone);
}
private void removeAlreadyUsedRecursiveContacts(SipServletResponseImpl resp) {
// make sure that the targets have not been used before
if ((resp.getStatus() / 100) == 3) {
Address contact = null;
URI uri = null;
Header contacts = resp.getRawHeader(Header.CONTACT);
contacts.setReadOnly(false);
try {
for (ListIterator<Address> li = contacts.getAddressValues();
li.hasNext();) {
contact = li.next();
uri = contact.getURI();
if ((uri != null) && (findBranch(uri) != null)) {
// remove contact
li.remove();
}
}
} catch (ServletParseException e) {
// have problem cleaning contacts, can't do much about it...
if (_log.isLoggable(Level.WARNING)) {
_log.log(Level.WARNING,
"Could not remove already recursed Contacts = " +
contacts);
}
} finally {
contacts.setReadOnly(true);
}
// if all contacts have been used this 3xx is already used
if (!resp.getHeaders(Header.CONTACT).hasNext()) {
resp.setAlreadyRedirected();
}
}
}
private void callDoBranchResponse(SipServletResponseImpl resp) {
// This is a branch response
resp.setBranchResponse(true);
if (resp.getRequestImpl().getSupervised()) {
// 3a. pass response to servlet
try {
// Design flaw in JSR289
SipApplication application = getSipApplication();
if (application == null) {
throw new IllegalStateException("Proxy without application");
}
switch (application.getApplicationVersion()) {
case VERSION_1_0:
// In version 1.0 we cannot call servlet
// because of backward compabitility problem
break;
default:
invokeServlet(resp);
}
} catch (Exception e) {
// problem in servlet, lets drop response
if (_log.isLoggable(Level.INFO)) {
_log.log(Level.INFO, "Problem in servlet.", e);
}
}
}
}
private void doInitialResponse(SipServletResponseImpl resp, ProxyContext pc) {
int status = resp.getStatus() / 100;
if (status != 1) {
if (status == 2) {
// 1. best response is found, stop new proxing
if (setAndTestBestResponse(resp) == null) {
// 2a. cancel all branches if cancel is allowed
if (!getNoCancel()) {
cancelIntern(null);
}
// 3a. invoke servlet if supervised.
// 4a. send upstream
invokeServletAndForward(resp, pc);
} else if (resp.getMethod().equals("INVITE")) {
// if multiple 2xx response to INVITE
// 3b. invoke servlet if supervised
// 3c. send upstream
invokeServletAndForward(resp, pc);
}
} else if (status == 6) {
// 1. stop new proxing.
if (!setAndTest6xx()) {
// 2. cancel all branches
cancelIntern(null);
// 3. find best response since no new branches can be made
SipServletResponseImpl bestResp = findBestResponse();
if (bestResp != null) {
if (setAndTestBestResponse(bestResp) == null) {
// 4. if supervised is true invoke servlet with best
// response and finally send back best response
invokeServletAndForward(bestResp, pc);
}
}
}
} else if ((status > 2) && (status < 6)) {
// 1. find best response
SipServletResponseImpl bestResp = findBestResponse();
if (bestResp != null) {
// 2. best response has been found, invoke servlet
// if supervised. Servlet might create new branches.
try {
if (acquireBestResponseCandidateState()) {
try {
if ((pc.getSipSession() != null) &&
pc.getSipSession().isValid()) {
assignSession(bestResp, pc);
bestResp.setBranchResponse(false);
if (bestResp.getRequestImpl().getSupervised()) {
// Pass response to servlet
invokeServlet(bestResp);
}
removeAlreadyUsedRecursiveContacts(bestResp);
// If servlet proxied we might not have a bestResp candidate anymore
bestResp = findBestResponse();
if (bestResp != null) {
if (setAndTestBestResponse(bestResp) == null) {
// Stop new proxing since best response
// has been found, send it back
dispatchAndClone(bestResp);
}
}
} else {
// if session is not valid lets send it upstreams
resp.popDispatcher().dispatch(resp);
}
} finally {
// Now we have attempted to do things in the
// BEST_RESPONSE_CANDIDATE state.
// Unless we actually have a confirmed best response
// we must go back to NO_BEST_RESPONSE state
// This is the case when the servlet has created new branches
resetReponseCandidateAcquisition();
}
}
} catch (Exception e) {
// problem in servlet, lets drop response
if (_log.isLoggable(Level.INFO)) {
_log.log(Level.INFO, "Problem in servlet.", e);
}
}
}
}
}
}
public void dispatch(SipServletResponseImpl resp, ProxyContext pc) {
if (_log.isLoggable(Level.FINEST)) {
_log.log(Level.FINEST, resp.toDebugString());
}
// forward to branch only if the response is initial,
// otherwise invoke servlet if supervised is set by request.
if (resp.getRequest().isInitial()) {
boolean hasBestResponse = hasBestResponse();
int respStatus = resp.getStatus() / 100;
if (!hasBestResponse || (respStatus == 1)) {
ProxyBranchImpl branch = findBranch(resp.getRequest()
.getRequestURI());
if ((branch != null) &&
((hasBestResponse == false) ||
((respStatus == 1) &&
(_bestResponse.getRequest().getRequestURI()
.equals(branch.getRequest()
.getRequestURI()) == false)))) {
// Calling doBranchResponse reponse BEFORE invoking servlet
// to enable status code change in doBranchResponse
// if neccesary.
// AND
// in case of a proxyTo() in doBranchResponse we want
// to append to SequentialTargetSet before invoking
// next mechanism
if (resp.getStatus() >= 300) {
callDoBranchResponse(resp);
}
if (branch.doInitialResponse(resp, pc)) {
SipServletResponseImpl bestResponseCandidate = resp;
if (((resp.getStatus() / 100) == 2) ||
((bestResponseCandidate = findBestResponse()) != null)) {
// this is a response of the virtual proxy branch (proxy as a UAS)
// and it was generated in doResponse callback and/or it is a 2xx
//
// JSR289 10.2.3 Sending Responses
// - If its a 2xx response, it is sent upstream immediately
// without notifying the application of its own response.
//
// - If the best response received was a non-2xx and the application
// generated its own final response in the doResponse callback
// (be it a 2xx or non-2xx), then that response is sent immediately
// without invoking the application again for its own generated response.
if (setAndTestBestResponse(bestResponseCandidate) == null) {
// lets stop retransmissions and subsequent request
// to be sent to servlet of proxy, forward all to ua.
bestResponseCandidate.getRequestImpl()
.setSupervised(false);
// stop new proxing since best response has
// been found, send it immediately back
dispatchAndClone(bestResponseCandidate);
}
}
} else {
doInitialResponse(resp, pc);
}
} else {
// no branch found, drop message
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"Could not find ProxyBranch of response: " + resp);
}
}
} else {
// best response already exist,
// only 2xx to INVITE should be forwarded
if (((resp.getStatus() / 100) == 2) &&
resp.getMethod().equals("INVITE")) {
invokeServletAndForward(resp, pc);
}
}
} else {
if (resp.getMethod().equals("CANCEL")) {
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"Response for CANCEL received. Drop. ");
}
return;
}
invokeServletAndForward(resp, pc);
}
}
public SipServletRequest getOriginalRequest() {
return getOriginalRequestImpl();
}
public SipServletRequestImpl getOriginalRequestImpl() {
return _originalRequest;
}
public SipServletRequestImpl cloneOriginalRequest() {
SipServletRequestImpl clone = (SipServletRequestImpl) _originalRequest.clone();
// should point to same proxy context instance
clone.setProxyContext(_originalRequest.getProxyContext());
// set transaction request
clone.setTransactionRequest(_originalRequest.getTransactionRequest());
return clone;
}
public void cancel() throws IllegalStateException {
cancel(null);
}
public void cancel(Header reason) throws IllegalStateException {
if (!hasBestResponse() && !has6xx()) {
cancelIntern(reason);
} else {
throw new IllegalStateException("Proxy has already completed");
}
}
public void cancel(java.lang.String[] protocol, int[] reasonCode,
java.lang.String[] reasonText) {
Header reasonHeader = makeReasonHeader(protocol, reasonCode, reasonText);
cancel(reasonHeader);
}
public void setNoCancel(boolean noCancel) {
_noCancel = noCancel;
}
public boolean getNoCancel() {
return _noCancel;
}
public void cancelIntern(Header reason) {
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE, "CANCEL proxy");
}
// lets clear all created targets
getCreatedTarget().getTargetSets().clear();
// should cancel all target sets
for (TargetSet t : getTargetSets()) {
t.cancel(reason);
}
}
public int getProxyTimeout() {
return _proxyTimeout;
}
@Deprecated
public int getSequentialSearchTimeout() {
return _proxyTimeout;
}
public void setProxyTimeout(int timeout) {
if (timeout <= 0) {
throw new IllegalArgumentException(
"Invalid ProxyTimeout must be > 0");
}
_proxyTimeout = timeout;
_originalRequest.setTimerC(timeout);
}
@Deprecated
public void setSequentialSearchTimeout(int timeout) {
setProxyTimeout(timeout);
}
/**
* Gets the Path URI
*
* @param req
*/
public SipURI getPathURI(final SipServletRequestImpl req) {
return new PathURI(req);
}
/**
* Gets the Record-Route URI
*
* @param req
*/
public SipURI getRecordRouteURI(final SipServletRequestImpl req) {
return new RecordRouteURI(req);
}
/**
* This will return the session of the original request or null if not found
*/
public SipSessionBase getSipSession() {
if (getOriginalRequestImpl() != null) {
return getOriginalRequestImpl().getSessionImpl();
} else {
return null;
}
}
public String toString() {
StringBuilder sb = new StringBuilder(super.toString() +
" isRecordRoute: ");
sb.append(getRecordRoute());
sb.append(" isStateful: ");
sb.append(getStateful());
return sb.toString();
}
private CreatedTargetSet getCreatedTarget() {
return _createdSet;
}
private ProxyBranchImpl createBranch(URI uri, boolean isVirtual) {
if (uri instanceof GeneralURIImpl) {
throw new IllegalArgumentException(
"Cannot create a branch for this URI scheme.");
}
SipServletRequestImpl req = createRequest(uri);
return isVirtual ? new VirtualProxyBranchImpl(this, req)
: new ProxyBranchImpl(this, req);
}
private SipServletRequestImpl createRequest(URI uri) {
TargetResolver.getInstance().updateDefaultTransportParameter(uri);
SipServletRequestImpl request = cloneOriginalRequest();
// update the request uri
request.setRequestURI(uri);
// update max forwards
int maxForwards = request.getMaxForwards();
if (maxForwards == -1) {
maxForwards = 70;
} else {
maxForwards--;
}
request.setMaxForwards(maxForwards);
return request;
}
public List<ProxyBranch> getProxyBranches() {
List<ProxyBranchImpl> branches = new ArrayList<ProxyBranchImpl>(5);
for (TargetSet t : getTargetSets()) {
t.addTopLevelBranch(branches);
}
for (TargetSet t : getCreatedTarget().getTargetSets()) {
t.addTopLevelBranch(branches);
}
return new ArrayList<ProxyBranch>(branches);
}
public ProxyBranch getProxyBranch(URI uri) {
ProxyBranch branch = findBranch(uri);
if (branch == null) {
branch = getCreatedTarget().findBranch(uri);
}
return branch;
}
private ProxyBranchImpl findBranch(URI uri) {
ProxyBranchImpl branch = null;
for (TargetSet t : getTargetSets()) {
branch = t.findBranch(uri);
if (branch != null) {
return branch;
}
}
return null;
}
public synchronized List<ProxyBranch> createProxyBranches(
List<?extends URI> targets) {
return createProxyBranchesIntern(targets);
}
private List<ProxyBranch> createProxyBranchesIntern(
List<?extends URI> targets) {
if ((targets == null) || (targets.size() == 0)) {
throw new IllegalArgumentException(
"Cannot proxy to an empty list of URIs");
}
// make sure that the targets have not been used before
for (URI uri : targets) {
if (findBranch(uri) != null) {
throw new IllegalStateException(
"Not allowed to proxy to same destination twice = " + uri);
}
}
List<ProxyBranch> list = new ArrayList<ProxyBranch>(targets.size());
ProxyBranchImpl branch = null;
for (URI uri : targets) {
branch = createBranch(uri, false);
getCreatedTarget().add(branch);
list.add(branch);
}
return list;
}
private List<ProxyBranchImpl> createRecursedProxyBranchesIntern(
List<?extends URI> targets) {
// make sure that the targets have not been used before
// Create a clone of the aggregation to iterate over for removal.
ArrayList<URI> clone = new ArrayList<URI>(5);
clone.addAll(targets);
for (URI uri : clone) {
if (findBranch(uri) != null) {
targets.remove(uri);
}
}
List<ProxyBranchImpl> list = new ArrayList<ProxyBranchImpl>(targets.size());
ProxyBranchImpl branch = null;
for (URI uri : targets) {
branch = createBranch(uri, false);
getCreatedTarget().add(branch);
list.add(branch);
}
return list;
}
/**
* Support of JSR 289 10.2.3 Sending Responses.
* Creates a ProxyBranch of virtual type (VirtualProxyBranch)
* to support a Proxy acting as a UAS. It also adds a new request
* with a virtual request uri (proxy_uas@localhost) pointing to
* this VirtualProxyBranch
* @param resp
*/
public synchronized void setVirtualProxyBranchRequest(
SipServletResponseImpl resp) {
if (_virtualProxyBranch == null) {
_virtualProxyBranch = createBranch(_virtualProxyBranchURI, true);
}
resp.setRequest(_virtualProxyBranch.getRequestImpl());
}
/**
* Support of JSR 289 10.2.3 Sending Responses.
* The created VirtualProxyBranch will be started (proxyTo).
* @param resp
*/
public synchronized void startVirtualProxyBranch(
SipServletResponseImpl resp) {
// must have created the branch before calling this method
if (_virtualProxyBranch != null) {
// guard to not start it twice
ProxyBranchImpl vpb = findBranch(_virtualProxyBranchURI);
if (vpb == null) {
// add this target set to the total set
getTargetSets().add(_virtualProxyBranch);
// lets simulate a proxy call
_virtualProxyBranch.proxyTo();
// set new dialog
resp.setDialog(resp.getRequestImpl().getDialog());
}
} else {
throw new IllegalStateException(
"Must first create VirtualProxyBranch");
}
}
/**
* Returns the response of 2xx. If no 2xx exist the response of 6xx is
* returned, otherwise the response of the lowest status number found is
* returned. If the target set is not finished or not started null is
* returned.
*
* @return the response of 2xx. If no 2xx exist the response of 6xx is
* returned, otherwise the response of the lowest status number found
* is returned. NOTE: if TargetSet is not finished or not started
* null is returned
*/
private synchronized SipServletResponseImpl findBestResponse() {
// lets assume target set has finished
boolean isRunning = false;
SipServletResponseImpl bestResponse = null;
SipServletResponseImpl currentResp = null;
int currentStatus = -1;
int bestStatus = 700; // one higher than possible
for (TargetSet t : getTargetSets()) {
currentResp = t.findBestResponse();
if (currentResp == null) {
// haven't got all answers, but 2xx might already be in set.
isRunning = true;
} else {
currentStatus = currentResp.getStatus();
// 2xx response should always be returned...
if ((currentStatus / 100) == 2) {
// can't be better, lets return...
return currentResp;
}
// ...otherwise save 6xx response (might find 2xx later)...
else if ((currentStatus / 100) == 6) {
bestStatus = currentStatus;
bestResponse = currentResp;
}
// ...otherwise save the response of the lowest status number
else if (((bestStatus / 100) != 6) &&
(bestStatus > currentResp.getStatus())) {
bestStatus = currentResp.getStatus();
bestResponse = currentResp;
}
}
}
if (isRunning) {
// haven't got all answers, lets wait for better response context
return null;
}
// have collected all answers
return bestResponse;
}
/**
* Sets best response.
*
* If already set, best response is returned and the proposed best response
* is left without notice. If not set best response is set and null is
* returned.
*
* @param resp
* @return any previous best response being set or null if it was empty
*/
private synchronized SipServletResponseImpl setAndTestBestResponse(
SipServletResponseImpl resp) {
if (_bestResponse != null) {
return _bestResponse;
}
_bestResponse = resp;
_bestResponse.setBranchResponse(false);
confirmBestResponse();
return null;
}
private synchronized boolean hasBestResponse() {
return _proxyState == ProxyState.BEST_RESPONSE;
}
/**
* Indicate that first 6xx has been received.
*
* If already set, true is returned. If not set false is returned and true is
* saved for next invocation.
*
* @return any previous set returns true otherwise false
*/
private synchronized boolean setAndTest6xx() {
if (_is6xx) {
return true;
}
_is6xx = true;
return false;
}
public synchronized boolean has6xx() {
return _is6xx;
}
private void startProxy(boolean isSaved) {
if (getCreatedTarget().getTargetSets().isEmpty()) {
throw new IllegalStateException(
"Must create branches before startProxy");
}
if (hasBestResponse() || has6xx()) {
throw new IllegalStateException("Proxy has already completed");
}
_originalRequest.setSentOnThread(true);
TargetSet t = null;
synchronized (this) {
if (hasNonFinishedSequentialTargetSet()) {
appendToNonFinishedSequentialTargetSet(getCreatedTarget()
.getTargetSets());
} else {
// move created target set to parallel or sequential target set
// and clear the created one...
if (getParallel()) {
t = new ParallelTargetSet(getCreatedTarget(), getRecurse());
} else {
t = new SequentialTargetSet(getCreatedTarget(), getRecurse());
}
}
getCreatedTarget().getTargetSets().clear();
if (isSaved) {
// add this target set to the total set
getTargetSets().add(t);
}
}
// time to proxy
t.proxyTo();
setProxyStarted();
}
public void proxyTo(SipServletRequestImpl req, URI uri)
throws IllegalStateException {
List<URI> uris = new ArrayList<URI>(1);
uris.add(uri);
proxyTo(req, uris);
}
public void proxyTo(SipServletRequestImpl req, List<?extends URI> uris)
throws IllegalStateException {
createProxyBranchesIntern(uris);
startProxy(true);
}
List<ProxyBranchImpl> recurseTo(SipServletRequestImpl req,
List<?extends URI> uris) throws IllegalStateException {
List<ProxyBranchImpl> l = createRecursedProxyBranchesIntern(uris);
// should be saved in ProxyBranch that started this recursion
if (!l.isEmpty()) {
startProxy(false);
}
return l;
}
/**
* Test whether this is the first ProxyBranch or not. Returns true for first
* caller and false otherwise.
*
* @return true for first caller and false otherwise
*/
public synchronized boolean isFirstProxyBranchSetAndTest() {
if (_isFirst) {
_isFirst = false;
return true;
}
return false;
}
/**
* Creates a proxy facade, related to the specified request, for this proxy.
*
* @param request
* the request to relate the proxy facade to
* @return a proxy facade for this proxy
*/
public Proxy getFacade(SipServletRequestImpl request) {
return new ProxyFacade(this, request);
}
public static void validateSetAddToPath(SipServletRequest request) {
if (!request.getMethod().equals("REGISTER")) {
throw new IllegalStateException(
"It's not allowed to call setAddToPath for non-REGISTER requests.");
}
}
/**
* Whether a request entering this proxy should record route. May be called before
* a dialog is established.
*
* @return
*/
private boolean shouldRecordRoute() {
if (hasBestResponse()) {
return dialogRecordRoute();
} else {
// Request has entered proxy before
// best response has been determined.
//
_log.log(Level.FINE,
"Proxy handling subsequent request before dialog is established");
//
// The only possibility I can see is PRACK and UPDATE.
// FIXME Find the ProxyBranch given from the to-tag and
// use setting on proxy branch level.
// FIXME Consult JSR289 EG to see if PRACK and UPDATE should obey
// to recordRoute setting.
//
// For now, lets just return setting on proxy level.
return getRecordRoute();
}
}
@Override
protected boolean dialogRecordRoute() {
if (!hasBestResponse()) {
throw new IllegalStateException("No established dialog");
}
ProxyBranch branch = findBranch(_bestResponse.getRequest()
.getRequestURI());
if (branch == null) {
throw new IllegalStateException(
"Branch object for best response is missing");
}
return branch.getRecordRoute();
}
public final boolean getRecordRoute() {
return _isRecordRoute;
}
public final void setRecordRoute(boolean recordRoute) {
if (isProxyStarted()) {
throw new IllegalStateException("Proxy is already started");
}
_isRecordRoute = recordRoute;
}
static Header makeReasonHeader(String[] protocol, int[] reasonCode,
String[] reasonText) throws IllegalArgumentException {
if ((protocol == null) && (reasonCode == null) && (reasonText == null)) {
return null;
}
if ((protocol == null) || (reasonCode == null) || (reasonText == null)) {
throw new IllegalArgumentException(
"At least one but not all of the arrays is null");
}
int length = protocol.length;
if ((reasonCode.length != length) || (reasonText.length != length)) {
throw new IllegalArgumentException("Arrays are of different length");
}
if (length == 0) {
return null;
}
for (int i = 0; i < length; i++) {
if (protocol[i] == null) {
throw new IllegalArgumentException(
"Requiring protocol for all Reason headers");
}
}
// All arrays are of equal length>0, all with protocol part.
MultiLineHeader reasonHeader = new MultiLineHeader(Header.REASON, false);
// Use one StringBuilder for efficiency
StringBuilder builder = new StringBuilder();
for (int i = 0; i < length; i++) {
builder.append(protocol[i]);
builder.append(" ;cause=");
builder.append(reasonCode[i]);
if (reasonText[i] != null) {
builder.append(" ;text=\"");
builder.append(reasonText[i]);
builder.append("\"");
}
reasonHeader.setValue(builder.toString(), false);
// reset builder
builder.setLength(0);
}
return reasonHeader;
}
private boolean hasNonFinishedSequentialTargetSet() {
return findNonFinishedSequentialTargetSet() != null;
}
private SequentialTargetSet findNonFinishedSequentialTargetSet() {
for (TargetSet targetSet : _targetSets) {
if (targetSet instanceof SequentialTargetSet) {
SequentialTargetSet sts = (SequentialTargetSet) targetSet;
if (sts.hasNext()) {
return sts;
}
}
}
return null;
}
private void appendToNonFinishedSequentialTargetSet(
List<TargetSet> targetSets) {
SequentialTargetSet sts = findNonFinishedSequentialTargetSet();
if (sts == null) {
throw new IllegalStateException(
"Could not find non finished sequential target set");
}
for (TargetSet targetSet : targetSets) {
sts.appendTargetSet(targetSet);
}
}
protected SipApplication getSipApplication() {
ServletDispatcher dispatcher = ApplicationDispatcher.getInstance()
.getServletDispatcher(getApplicationSessionName());
return (dispatcher == null) ? null : dispatcher.getSipApplicationModel();
}
void validateOutboundInterface(InetAddress ia) {
if (ia == null) {
throw new NullPointerException();
}
SipServletRequestImpl impl = getOriginalRequestImpl();
if (!impl.getSession().isValid()) {
throw new IllegalStateException("Session is not valid");
}
}
/**
* This class is a facade for setting record-route parameters of a request.
*/
static final class RecordRouteURI implements SipURI {
private static final String SET_RR_COMPONENTS_DISALLOWED = "Not allowed to set Record-Route components.";
private static final String SET_RR_PARAMS_DISALLOWED = "Not allowed to set SIP URI parameters on Record-Route.";
private final SipServletRequestImpl req;
RecordRouteURI(SipServletRequestImpl req) {
this.req = req;
}
public SipURI clone() {
throw new UnsupportedOperationException();
}
public String getHeader(String name) {
throw new UnsupportedOperationException();
}
public Iterator<String> getHeaderNames() {
throw new UnsupportedOperationException();
}
public String getHost() {
throw new UnsupportedOperationException();
}
public boolean getLrParam() {
throw new UnsupportedOperationException();
}
public String getMAddrParam() {
throw new UnsupportedOperationException();
}
public String getMethodParam() {
throw new UnsupportedOperationException();
}
public String getParameter(String name) {
return req.getRecordRouteURIParam(name);
}
public Iterator<String> getParameterNames() {
return req.getRecordRouteURIParamNames();
}
public int getPort() {
throw new UnsupportedOperationException();
}
public int getTTLParam() {
throw new UnsupportedOperationException();
}
public String getTransportParam() {
throw new UnsupportedOperationException();
}
public String getUser() {
throw new UnsupportedOperationException();
}
public String getUserParam() {
throw new UnsupportedOperationException();
}
public String getUserPassword() {
throw new UnsupportedOperationException();
}
public boolean isSecure() {
throw new UnsupportedOperationException();
}
public void removeParameter(String name) {
req.removeRecordRouteURIParam(name);
}
public void setHeader(String name, String value) {
throw new UnsupportedOperationException();
}
public void removeHeader(String name) {
throw new UnsupportedOperationException();
}
public void setHost(String host) {
throw new IllegalArgumentException(SET_RR_COMPONENTS_DISALLOWED);
}
public void setLrParam(boolean lr) {
throw new IllegalArgumentException(SET_RR_PARAMS_DISALLOWED);
}
public void setMAddrParam(String mAddr) {
throw new IllegalArgumentException(SET_RR_PARAMS_DISALLOWED);
}
public void setMethodParam(String methodParam) {
throw new IllegalArgumentException(SET_RR_PARAMS_DISALLOWED);
}
public void setParameter(String name, String value) {
req.setRecordRouteURIParam(name, value);
}
public void setPort(int port) {
throw new IllegalArgumentException(SET_RR_COMPONENTS_DISALLOWED);
}
public void setSecure(boolean sips) {
throw new IllegalArgumentException(SET_RR_COMPONENTS_DISALLOWED);
}
public void setTTLParam(int ttl) {
throw new IllegalArgumentException(SET_RR_PARAMS_DISALLOWED);
}
public void setTransportParam(String transport) {
throw new IllegalArgumentException(SET_RR_PARAMS_DISALLOWED);
}
public void setUser(String user) {
throw new UnsupportedOperationException();
}
public void setUserParam(String userParam) {
throw new IllegalArgumentException(SET_RR_PARAMS_DISALLOWED);
}
public void setUserPassword(String password) {
throw new UnsupportedOperationException();
}
public String getScheme() {
throw new UnsupportedOperationException();
}
public boolean isSipURI() {
throw new UnsupportedOperationException();
}
}
/**
* This class is a facade for setting Path parameters of a REGISTER request.
* Assume same parameters are OK to set as for Record-Route
*/
static final class PathURI implements SipURI {
private static final String SET_PATH_COMPONENTS_DISALLOWED = "Not allowed to set Path components.";
private static final String SET_PATH_PARAMS_DISALLOWED = "Not allowed to set SIP URI parameters on Path.";
private final SipServletRequestImpl req;
PathURI(SipServletRequestImpl req) {
this.req = req;
}
public SipURI clone() {
throw new UnsupportedOperationException();
}
public String getHeader(String name) {
throw new UnsupportedOperationException();
}
public Iterator getHeaderNames() {
throw new UnsupportedOperationException();
}
public String getHost() {
throw new UnsupportedOperationException();
}
public boolean getLrParam() {
throw new UnsupportedOperationException();
}
public String getMAddrParam() {
throw new UnsupportedOperationException();
}
public String getMethodParam() {
throw new UnsupportedOperationException();
}
public String getParameter(String name) {
return req.getPathURIParam(name);
}
public Iterator getParameterNames() {
return req.getPathURIParamNames();
}
public int getPort() {
throw new UnsupportedOperationException();
}
public int getTTLParam() {
throw new UnsupportedOperationException();
}
public String getTransportParam() {
throw new UnsupportedOperationException();
}
public String getUser() {
throw new UnsupportedOperationException();
}
public String getUserParam() {
throw new UnsupportedOperationException();
}
public String getUserPassword() {
throw new UnsupportedOperationException();
}
public boolean isSecure() {
throw new UnsupportedOperationException();
}
public void removeParameter(String name) {
req.removePathURIParam(name);
}
public void setHeader(String name, String value) {
throw new UnsupportedOperationException();
}
public void removeHeader(String name) {
throw new UnsupportedOperationException();
}
public void setHost(String host) {
throw new IllegalArgumentException(SET_PATH_COMPONENTS_DISALLOWED);
}
public void setLrParam(boolean lr) {
throw new IllegalArgumentException(SET_PATH_PARAMS_DISALLOWED);
}
public void setMAddrParam(String mAddr) {
throw new IllegalArgumentException(SET_PATH_PARAMS_DISALLOWED);
}
public void setMethodParam(String methodParam) {
throw new IllegalArgumentException(SET_PATH_PARAMS_DISALLOWED);
}
public void setParameter(String name, String value) {
req.setPathURIParam(name, value);
}
public void setPort(int port) {
throw new IllegalArgumentException(SET_PATH_COMPONENTS_DISALLOWED);
}
public void setSecure(boolean sips) {
throw new IllegalArgumentException(SET_PATH_COMPONENTS_DISALLOWED);
}
public void setTTLParam(int ttl) {
throw new IllegalArgumentException(SET_PATH_PARAMS_DISALLOWED);
}
public void setTransportParam(String transport) {
throw new IllegalArgumentException(SET_PATH_PARAMS_DISALLOWED);
}
public void setUser(String user) {
throw new UnsupportedOperationException();
}
public void setUserParam(String userParam) {
throw new IllegalArgumentException(SET_PATH_PARAMS_DISALLOWED);
}
public void setUserPassword(String password) {
throw new UnsupportedOperationException();
}
public String getScheme() {
throw new UnsupportedOperationException();
}
public boolean isSipURI() {
throw new UnsupportedOperationException();
}
}
/**
* This class is a facade to a proxy and relates the proxy to a certain
* request.
*
* @author ejoelbi
*
*/
static class ProxyFacade implements Proxy {
private ProxyImpl delegate;
private SipServletRequestImpl req;
public ProxyFacade(ProxyImpl proxy, SipServletRequestImpl reqImpl) {
this.delegate = proxy;
this.req = reqImpl;
}
public void cancel() {
delegate.cancel();
}
public void cancel(String[] protocol, int[] reasonCode,
String[] reasonText) {
delegate.cancel(protocol, reasonCode, reasonText);
}
public void setNoCancel(boolean noCancel) {
delegate.setNoCancel(noCancel);
}
public boolean getNoCancel() {
return delegate.getNoCancel();
}
public SipServletRequest getOriginalRequest() {
return delegate.getOriginalRequest();
}
public boolean getParallel() {
return delegate.getParallel();
}
public boolean getRecordRoute() {
return delegate.getRecordRoute();
}
public SipURI getRecordRouteURI() {
if (!delegate.getRecordRoute()) {
throw new IllegalStateException("Record-Routing is not enabled");
}
return delegate.getRecordRouteURI(req);
}
public boolean getRecurse() {
return delegate.getRecurse();
}
public int getSequentialSearchTimeout() {
return delegate.getSequentialSearchTimeout();
}
public boolean getStateful() {
return delegate.getStateful();
}
public boolean getSupervised() {
return req.getSupervised();
}
public void proxyTo(URI uri) {
delegate.proxyTo(req, uri);
}
public void proxyTo(List uris) {
delegate.proxyTo(req, uris);
}
public void setParallel(boolean parallel) {
delegate.setParallel(parallel);
}
public void setRecordRoute(boolean recordRoute) {
delegate.setRecordRoute(recordRoute);
}
public void setRecurse(boolean recurse) {
delegate.setRecurse(recurse);
}
public void setSequentialSearchTimeout(int seconds) {
delegate.setSequentialSearchTimeout(seconds);
}
public void setStateful(boolean stateful) {
delegate.setStateful(stateful);
}
public void setSupervised(boolean supervised) {
req.setSupervised(supervised);
}
public List<ProxyBranch> createProxyBranches(List<?extends URI> targets) {
return delegate.createProxyBranches(targets);
}
public boolean getAddToPath() {
return delegate.getAddToPath();
}
public SipURI getPathURI() {
if (!delegate.getAddToPath()) {
throw new IllegalStateException("addToPath is not enabled");
}
return delegate.getPathURI(req);
}
public ProxyBranch getProxyBranch(URI uri) {
return delegate.getProxyBranch(uri);
}
public List<ProxyBranch> getProxyBranches() {
return delegate.getProxyBranches();
}
public int getProxyTimeout() {
return delegate.getProxyTimeout();
}
public void setAddToPath(boolean addToPath) {
// validateSetAddToPath(req);
if (req.getMethod().equals("REGISTER")) {
delegate.setAddToPath(addToPath);
}
// else ignore as spec and TCK don't like exceptions
}
/**
* Not doing anything inside this method
*/
public void setOutboundInterface(InetAddress address) {
delegate.validateOutboundInterface(address);
delegate.setOutboundInterface(new OutboundInterface(address));
}
/**
* Not doing anything inside this method
*/
public void setOutboundInterface(InetSocketAddress address) {
delegate.validateOutboundInterface(address.getAddress());
delegate.setOutboundInterface(new OutboundInterface(address));
}
public void setProxyTimeout(int seconds) {
delegate.setProxyTimeout(seconds);
}
public void startProxy() {
delegate.startProxy(true);
}
boolean hasBestRepsonse() {
return delegate.hasBestResponse();
}
}
/**
* Proxy State in context of whether best response has been selected or not.
*
* At first the proxy is in NO_BEST_RESPONSE state. This means that no thread has taken
* any action to try to select best response. Once the findBestReponse() answer with a
* non-null value it means that the thread may attempt to call the servlet. To avoid several
* threads to call the servlet it can put it in state BEST_RESPONSE_CANDIDATE. During in that
* state the servlet is called and the servlet can do either
* - proxy to a new leg
* - do nothing with branches
* - create a virtual proxy branch with a potentially better response
*
* If a new leg is created the state must go back to NO_BEST_RESPONSE. For the other cases there will
* be a best response and hence the state goes to BEST_RESPONSE.
*
* In some cases you can go straight from NO_BEST_RESPONSE to BEST_RESPONSE, e.g. when a 2xx arrive.
*
* @author qmaghes
*
*/
enum ProxyState {NO_BEST_RESPONSE,
BEST_RESPONSE_CANDIDATE,
BEST_RESPONSE;
}
}