Package com.ericsson.ssa.sip

Source Code of com.ericsson.ssa.sip.ProxyImpl$ProxyFacade

/*
* 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;
    }
}
TOP

Related Classes of com.ericsson.ssa.sip.ProxyImpl$ProxyFacade

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.