FileDocCategorySizeDatePackage
Transaction.javaAPI DocphoneME MR2 API (J2ME)31315Wed May 02 18:00:42 BST 2007gov.nist.siplite.stack

Transaction.java

/*
 * Portions Copyright  2000-2007 Sun Microsystems, Inc. All Rights
 * Reserved.  Use is subject to license terms.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */
package gov.nist.siplite.stack;

import gov.nist.siplite.parser.*;
import gov.nist.siplite.header.*;
import gov.nist.siplite.message.*;
import gov.nist.siplite.*;
import gov.nist.siplite.address.*;
import gov.nist.core.*;
import java.util.*;

import java.io.IOException;
import javax.microedition.sip.SipException;

import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;

/**
 * Abstract class to support both client and server transactions.
 * Provides an encapsulation of a message channel, handles timer events,
 * and creation of the Via header for a message.
 *
 * @version JAIN-SIP-1.1
 *
 */
public abstract class Transaction
        extends MessageChannel {
    /** Transaction timer interval. */
    protected static final int BASE_TIMER_INTERVAL =
            SIPTransactionStack.BASE_TIMER_INTERVAL;
    /** RTT Estimate. 500ms default. */
    protected static final int T1 = 500/BASE_TIMER_INTERVAL;
    /** 5 sec Maximum duration a message will remain in the network */
    protected static final int T4 = 5000/BASE_TIMER_INTERVAL;
    /**
     * The maximum retransmit interval for non-INVITE
     * requests and INVITE responses
     */
    protected static final int T2 = 4000/BASE_TIMER_INTERVAL;
    /** INVITE request retransmit interval, for UDP only */
    protected static final int TIMER_A = 1*T1;
    /** INVITE transaction timeout timer */
    protected static final int TIMER_B = 64*T1;
    /** INVITE transaction timeout timer */
    protected static final int TIMER_J = 64*T1;
    /** INVITE transaction timeout timer */
    protected static final int TIMER_F = 64*T1;
    /** INVITE transaction timeout timer */
    protected static final int TIMER_H = 64*T1;
    /** INVITE transaction timeout timer */
    protected static final int TIMER_I = T4;
    /** INVITE transaction timeout timer */
    protected static final int TIMER_K = T4;
    /** INVITE transaction timeout timer */
    protected static final int TIMER_D = 32000/BASE_TIMER_INTERVAL;
    /** INVITE transaction timeout timer */
    protected static final int TIMER_C = 3*60*1000/BASE_TIMER_INTERVAL;
    /** Last response message. */
    protected Response lastResponse;
    /** Current SIP dialog. */
    protected Dialog dialog;
    /** Flag indicating an ACK was received. */
    protected boolean ackSeenFlag;
    /** Flag indicating listener waiting. */
    protected boolean toListener;
    /**
     * Initialized but no state assigned.
     */
    public static final int INITIAL_STATE = -1;
    /**
     * Trying state.
     */
    public static final int TRYING_STATE = 1;
    /**
     * CALLING State.
     */
    public static final int CALLING_STATE = 2;
    /**
     * Proceeding state.
     */
    public static final int PROCEEDING_STATE = 3;
    /**
     * Completed state.
     */
    public static final int COMPLETED_STATE = 4;
    /**
     * Confirmed state.
     */
    public static final int CONFIRMED_STATE = 5;
    /**
     * Terminated state.
     */
    public static final int TERMINATED_STATE = 6;
    /**
     * Maximum number of ticks between retransmissions.
     */
    protected static final int MAXIMUM_RETRANSMISSION_TICK_COUNT =
        4000/BASE_TIMER_INTERVAL;
    /** Parent stack for this transaction. */
    protected SIPTransactionStack parentStack;
    /** Original request that is being handled by this transaction. */
    private Request originalRequest;
    /**
     * Underlying channel being used to send messages for this
     * transaction.
     */
    protected MessageChannel encapsulatedChannel;
    /** Transaction branch ID. */
    private String branch;
    /** Current transaction state. */
    private int currentState;
    /** Number of ticks the retransmission timer was set to last. */
    private int retransmissionTimerLastTickCount;
    /** Number of ticks before the message is retransmitted. */
    private int retransmissionTimerTicksLeft;
    /** Number of ticks before the transaction times out. */
    private int timeoutTimerTicksLeft;
    /** List of event listeners for this transaction. */
    private Vector eventListeners;
    /** Flag to indcate that this has been cancelled. */
    protected boolean isCancelled;
    /**
     * Object representing the connection being held by the JSR180
     * Implementation It can be either a SipClientConnection in case
     * of a ClientTransaction or a SipConnectionNotifier in case of a
     *ServerTransaction.
     */
    protected Object applicationData;

    /**
     * Retrieves the application data.
     * @return Object representing the connection being held by the JSR180
     * Implementation. It can be either a SipClientConnection in case of a
     * ClientTransaction or a SipConnectionNotifier in case of a
     * ServerTransaction
     */
    public Object getApplicationData() {
        return applicationData;
    }

    /**
     * Sets the application data.
     * @param newApplicationData Object representing the connection being held
     * by the JSR180
     * Implementation. It can be either a SipClientConnection in case of a
     * ClientTransaction or a SipConnectionNotifier in case of a
     * ServerTransaction
     */
    public void setApplicationData(Object newApplicationData) {
        applicationData = newApplicationData;
    }

    /**
     * Gets the branch identifier.
     * @return the current branch id
     */
    public String getBranchId() {
        return branch;
    }

    /**
     * Transaction constructor.
     *
     * @param newParentStack Parent stack for this transaction.
     * @param newEncapsulatedChannel
     * Underlying channel for this transaction.
     */
    protected Transaction(
            SIPTransactionStack newParentStack,
            MessageChannel newEncapsulatedChannel) {
        parentStack = newParentStack;
        encapsulatedChannel = newEncapsulatedChannel;
        currentState = INITIAL_STATE;
        disableRetransmissionTimer();
        disableTimeoutTimer();
        eventListeners = new Vector();
        // Always add the parent stack as a listener
        // of this transaction
        addEventListener(newParentStack);
    }

    /**
     * Sets the request message that this transaction handles.
     *
     * @param newOriginalRequest Request being handled.
     */
    public void setOriginalRequest(
            Request newOriginalRequest) {
        // Branch value of topmost Via header
        String newBranch;

        originalRequest = newOriginalRequest;
        originalRequest.setTransaction(this);
        // If the message has an explicit branch value set,
        newBranch = ((ViaHeader)newOriginalRequest.getViaHeaders().
                getFirst()).getBranch();
        if (newBranch != null) {
            if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                    "Setting Branch id : " + newBranch);
            }

            // Override the default branch with the one
            // set by the message
            setBranch(newBranch);
        } else {
            if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                    "Branch id is null!" + newOriginalRequest.encode());
            }
        }
    }

    /**
     * Gets the request being handled by this transaction.
     * @return Request being handled.
     */
    public Request getOriginalRequest() {
        return originalRequest;
    }

    /**
     * Gets the original request but cast to a Request structure.
     * @return the request that generated this transaction.
     */
    public Request getRequest() {
        return (Request) originalRequest;
    }

    /**
     * Returns a flag stating whether this transaction is for an
     * INVITE request or not.
     * @return True if this is an INVITE request, false if not.
     */
    protected final boolean isInviteTransaction() {
        return originalRequest.getMethod().equals(Request.INVITE);
    }

    /**
     * Returns true if the transaction corresponds to a CANCEL message.
     * @return true if the transaciton is a CANCEL transaction.
     */
    protected final boolean isCancelTransaction() {
        return originalRequest.getMethod().equals(Request.CANCEL);
    }

    /**
     * Returns a flag that states if this is a BYE transaction.
     * @return true if the transaciton is a BYE transaction.
     */
    protected final boolean isByeTransaction() {
        return originalRequest.getMethod().equals(Request.BYE);
    }

    /**
     * Returns the message channel used for
     * transmitting/receiving messages
     * for this transaction. Made public in support of JAIN dual
     * transaction model.
     * @return Encapsulated MessageChannel.
     */
    public MessageChannel getMessageChannel() {
        return encapsulatedChannel;
    }

    /**
     * Sets the Via header branch parameter used to identify
     * this transaction.
     * @param newBranch New string used as the branch
     * for this transaction.
     */
    public final void setBranch(String newBranch) {
        branch = newBranch;
    }

    /**
     * Gets the current setting for the branch parameter of this transaction.
     * @return Branch parameter for this transaction.
     */
    public final String getBranch() {
        if (branch == null) {
            branch = getOriginalRequest().getTopmostVia().getBranch();
        }
        return branch;
    }

    /**
     * Changes the state of this transaction.
     * @param newState New state of this transaction.
     */
    public void setState(int newState) {
        currentState = newState;

        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                "setState " + this + " " + newState);
        }

        // If this transaction is being terminated,
        if (newState == TERMINATED_STATE) {
            if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                    "Transaction is being terminated!");
                // new Exception().printStackTrace();
            }
        }
    }

    /**
     * Gets the current state of this transaction.
     * @return Current state of this transaction.
     */
    public final int getState() {
        return currentState;
    }

    /**
     * Enables retransmission timer events for this transaction to begin in
     * one tick.
     */
    protected final void enableRetransmissionTimer() {
        enableRetransmissionTimer(1);
    }

    /**
     * Enables retransmission timer events for this
     * transaction to begin after the number of ticks passed to
     * this routine.
     * @param tickCount Number of ticks before the
     * next retransmission timer
     * event occurs.
     */
    protected final void enableRetransmissionTimer(int tickCount) {
        retransmissionTimerTicksLeft =
                Math.min(tickCount, MAXIMUM_RETRANSMISSION_TICK_COUNT);
        retransmissionTimerLastTickCount =
                retransmissionTimerTicksLeft;
    }

    /**
     * Turns off retransmission events for this transaction.
     */
    protected final void disableRetransmissionTimer() {
        retransmissionTimerTicksLeft = -1;
    }

    /**
     * Enables a timeout event to occur for this transaction after the number
     * of ticks passed to this method.
     * @param tickCount Number of ticks before this transaction times out.
     */
    protected final void enableTimeoutTimer(int tickCount) {
        timeoutTimerTicksLeft = tickCount;
    }

    /**
     * Disabled the timeout timer.
     */
    protected final void disableTimeoutTimer() {
        timeoutTimerTicksLeft = -1;
    }

    /**
     * Fired after each timer tick.
     * Checks the retransmission and timeout
     * timers of this transaction, and fired these events
     * if necessary.
     */
    synchronized final void fireTimer() {
        // If the timeout timer is enabled,
        if (timeoutTimerTicksLeft != -1) {
            // Count down the timer, and if it has run out,
            if (--timeoutTimerTicksLeft == 0) {
                // Fire the timeout timer
                fireTimeoutTimer();
            }
        }
        // If the retransmission timer is enabled,
        if (retransmissionTimerTicksLeft != -1) {
            // Count down the timer, and if it has run out,
            if (--retransmissionTimerTicksLeft == 0) {
                // Enable this timer to fire again after
                // twice the original time
                enableRetransmissionTimer
                        (retransmissionTimerLastTickCount * 2);
                // Fire the timeout timer
                fireRetransmissionTimer();
            }
        }
    }

    /**
     * Tests a message to see if it is part of this transaction.
     * @param messageToHeaderTest message to be processed
     * @return True if the message is part of this
     * transaction, false if not.
     */
    public abstract boolean
            isMessagePartOfTransaction(Message messageToHeaderTest);

    /**
     * This method is called when this transaction's
     * retransmission timer has fired.
     */
    protected abstract void fireRetransmissionTimer();

    /**
     * This method is called when this transaction's
     * timeout timer has fired.
     */
    protected abstract void fireTimeoutTimer();

    /**
     * Tests if this transaction has terminated.
     * @return Trus if this transaction is terminated, false if not.
     */
    protected final boolean isTerminated() {
        return (getState() == TERMINATED_STATE);
    }

    /**
     * Gets the host.
     * @return the host
     */
    public String getHost() {
        return encapsulatedChannel.getHost();
    }

    /**
     * Gets the key.
     * @return the key
     */
    public String getKey() {
        return encapsulatedChannel.getKey();
    }

    /**
     * Gets the port.
     * @return the port
     */
    public int getPort() {
        return encapsulatedChannel.getPort();
    }

    /**
     * Gets the SIP stack context.
     * @return the SIP Stack
     */
    public SIPMessageStack getSIPStack() {
        return parentStack;
    }

    /**
     * Gets the remote address.
     * @return the remote address
     */
    public String getPeerAddress() {
        return encapsulatedChannel.getPeerAddress();
    }

    /**
     * Gets the remote port number.
     * @return the remote port number
     */
    public int getPeerPort() {
        return encapsulatedChannel.getPeerPort();
    }

    /**
     * Gets the connection transport.
     * @return the connection transport
     */
    public String getTransport() {
        return encapsulatedChannel.getTransport();
    }

    /**
     * Checks if the connection is reliable
     * @return true if channel is on a stream connection
     */
    public boolean isReliable() {
        return encapsulatedChannel.isReliable();
    }

    /**
     * Returns the Via header for this channel. Gets the Via header of the
     * underlying message channel, and adds a branch parameter to it for this
     * transaction.
     * @return the via header
     */
    public ViaHeader getViaHeader() {
        // Via header of the encapulated channel
        ViaHeader channelViaHeader;
        // Add the branch parameter to the underlying
        // channel's Via header
        channelViaHeader = super.getViaHeader();
        channelViaHeader.setBranch(branch);
        return channelViaHeader;
    }

    /**
     * Process an exception.
     * @param ex the exception to handle
     */
    public void handleException(SIPServerException ex) {
        encapsulatedChannel.handleException(ex);
    }

    /**
     * Processes the message through the transaction and sends it to the SIP
     * peer.
     * @param messageToHeaderSend Message to send to the SIP peer.
     */
    abstract public void sendMessage(Message messageToHeaderSend)
    throws IOException;

    /**
     * Parses the byte array as a message, process it through the
     * transaction, and send it to the SIP peer.
     *
     * @param messageBytes Bytes of the message to send.
     * @param receiverAddress Address of the target peer.
     * @param receiverPort Network port of the target peer.
     *
     * @throws IOException If there is an error parsing
     * the byte array into an object.
     */
    protected void sendMessage(
            byte[] messageBytes,
            String receiverAddress,
            int receiverPort)
            throws IOException {
        // Object representation of the SIP message
        Message messageToHeaderSend;
        try {
            StringMsgParser messageParser = new StringMsgParser();
            messageToHeaderSend =
                    messageParser.parseSIPMessage(messageBytes);
            sendMessage(messageToHeaderSend);
        } catch (ParseException e) {
            throw new IOException(e.getMessage());
        }
    }

    /**
     * Adds a new event listener to this transaction.
     * @param newListener Listener to add.
     */
    public void addEventListener(
            SIPTransactionEventListener newListener) {
        eventListeners.addElement(newListener);
    }

    /**
     * Removes an event listener from this transaction.
     * @param oldListener Listener to remove.
     */
    public void removeEventListener(
            SIPTransactionEventListener oldListener) {
        eventListeners.removeElement(oldListener);
    }

    /**
     * Creates a SIPTransactionErrorEvent and sends it
     * to all of the listeners of this transaction.
     * This method also flags the transaction as
     * terminated.
     * @param errorEventID ID of the error to raise.
     */
    protected void raiseErrorEvent(
            int errorEventID) {
        // Error event to send to all listeners
        SIPTransactionErrorEvent newErrorEvent;
        // Iterator through the list of listeners
        Enumeration listenerIterator;
        // Next listener in the list
        SIPTransactionEventListener nextListener;
        // Create the error event
        newErrorEvent = new SIPTransactionErrorEvent(this,
                errorEventID);
        // Loop through all listeners of this transaction
        synchronized (eventListeners) {
            listenerIterator = eventListeners.elements();
            while (listenerIterator.hasMoreElements()) {
                // Send the event to the next listener
                nextListener = (SIPTransactionEventListener)
                listenerIterator.nextElement();
                nextListener.transactionErrorEvent
                        (newErrorEvent);
            }
        }
        // Clear the event listeners after propagating the error.
        eventListeners.removeAllElements();
        // Errors always terminate a transaction
        setState(TERMINATED_STATE);
        if (this instanceof ServerTransaction &&
                this.isByeTransaction() && this.dialog != null)
            this.dialog.setState(Dialog.TERMINATED_STATE);
    }

    /**
     * A shortcut way of telling if we are a server transaction.
     * @return true if this is a servertransaction
     */
    protected boolean IsServerTransaction() {
        return this instanceof ServerTransaction;
    }

    /**
     * Gets the dialog object of this Transaction object. This object
     * returns null if no dialog exists. A dialog only exists for a
     * transaction when a session is setup between a User Agent Client and a
     * User Agent Server, either by a 1xx Provisional Response for an early
     * dialog or a 200OK Response for a committed dialog.
     * @return the Dialog Object of this Transaction object.
     * @see Dialog
     */
    public Dialog getDialog() {
        return dialog;
    }

    /**
     * Sets the dialog object.
     * @param newDialog the dialog to set.
     */
    public void setDialog(Dialog newDialog) {
        dialog = newDialog;
    }

    /**
     * Returns the current value of the retransmit timer in
     * milliseconds used to retransmit messages over unreliable transports.
     * @return the integer value of the retransmit timer in milliseconds.
     */
    public int getRetransmitTimer() {
        return SIPTransactionStack.BASE_TIMER_INTERVAL;
    }

    /**
     * Gets the host to assign for an outgoing Request via header.
     * @return the via host
     */
    public String getViaHost() {
        return getViaHeader().getHost();
    }

    /**
     * Gets the last response.
     * @return the last response
     */
    public Response getLastResponse() { return this.lastResponse; }

    /**
     * Gets the transaction Id.
     * @return the transaction id
     */
    public String getTransactionId() {
        return getOriginalRequest().getTransactionId();
    }

    /**
     * Gets the port to assign for the via header of an outgoing message.
     * @return the via port number
     */
    public int getViaPort() {
        return getViaHeader().getPort();
    }

    /**
     * A method that can be used to test if an incoming request
     * belongs to this transction. This does not take the transaction
     * state into account when doing the check otherwise it is identical
     * to isMessagePartOfTransaction. This is useful for checking if
     * a CANCEL belongs to this transaction.
     * @param requestToHeaderTest is the request to test.
     * @return true if the the request belongs to the transaction.
     */
    public boolean doesCancelMatchTransaction(
            Request requestToHeaderTest) {
        // List of Via headers in the message to test
        ViaList viaHeaders;
        // ToHeaderpmost Via header in the list
        ViaHeader topViaHeader;
        // Branch code in the topmost Via header
        String messageBranch;
        // Flags whether the select message is part of this transaction
        boolean transactionMatches;
        transactionMatches = false;

        if (getOriginalRequest() == null ||
                getOriginalRequest().getMethod().equals(Request.CANCEL)) {
            return false;
        }

        // Get the topmost Via header and its branch parameter
        viaHeaders = requestToHeaderTest.getViaHeaders();
        if (viaHeaders != null) {
            topViaHeader = (ViaHeader)viaHeaders.getFirst();
            messageBranch = topViaHeader.getBranch();
            if (messageBranch != null) {
                // If the branch parameter exists but
                // does not start with the magic cookie,
                if (!messageBranch.toUpperCase().startsWith(SIPConstants.
                        GENERAL_BRANCH_MAGIC_COOKIE.toUpperCase())) {
                    // Flags this as old
                    // (RFC2543-compatible) client
                    // version
                    messageBranch = null;
                }
            }
            // If a new branch parameter exists,
            if (messageBranch != null &&
                    this.getBranch() != null) {
                // If the branch equals the branch in
                // this message,
                if (getBranch().equals(messageBranch)
                && topViaHeader.getSentBy().
                        equals(((ViaHeader)getOriginalRequest().
                        getViaHeaders().getFirst()).
                        getSentBy())) {
                    transactionMatches = true;

                    if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                        Logging.report(Logging.INFORMATION,
                            LogChannels.LC_JSR180, "returning true");
                    }
                }
            } else {
                // If this is an RFC2543-compliant message,
                // If RequestURI, ToHeader tag, FromHeader tag,
                // CallIdHeader, CSeqHeader number, and top Via
                // headers are the same,
                if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                    Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                        "testing against " + getOriginalRequest());
                }

                if (

                        getOriginalRequest().getRequestURI().equals
                        (requestToHeaderTest.getRequestURI()) &&
                        getOriginalRequest().getTo().equals
                        (requestToHeaderTest.getTo()) &&
                        getOriginalRequest().getFromHeader().equals
                        (requestToHeaderTest.getFromHeader()) &&
                        getOriginalRequest().getCallId().
                        getCallId().equals
                        (requestToHeaderTest.getCallId() .getCallId()) &&
                        getOriginalRequest().
                        getCSeqHeader().getSequenceNumber() ==
                        requestToHeaderTest.getCSeqHeader().
                        getSequenceNumber() &&
                        topViaHeader.equals
                        (getOriginalRequest().
                        getViaHeaders().getFirst())) {
                    transactionMatches = true;
                }
            }
        }
        return transactionMatches;
    }

    /**
     * Checks if transaction has been sent to the listener.
     * @return true if transaction has been sent
     */
    public boolean passToListener() {
        return toListener;
    }

    /**
     * Closes the encapsulated channel.
     */
    public void close() {
        encapsulatedChannel.close();
    }

    /**
     * Check if this connection is secure.
     * @return true if this is a secure channel
     */
    public boolean isSecure() {
        return encapsulatedChannel.isSecure();
    }

    /**
     * Gets the message processor handling this transaction.
     * @return the mesage processor for this transaction
     */
    public MessageProcessor getMessageProcessor() {
        return encapsulatedChannel.getMessageProcessor();
    }

    /**
     * Sets the ACK has been seen flag.
     */
    public void setAckSeen() {
        ackSeenFlag = true;
    }

    /**
     * Checks if the ACK has been seen flag is set.
     * @return true if the ACK has been seen
     */
    public boolean isAckSeen() {
        return ackSeenFlag;
    }

    /**
     * Create route set for request.
     *
     * @param request the input request
     * @throws SipException if any occurs
     */
    protected void buildRouteSet(Request request) throws SipException {
        Message origMsg = lastResponse;
        boolean isServer = false;
        Dialog dlg = getDialog();

        if (dlg != null) { // inside of dialog
            Transaction firstTransaction = dlg.getFirstTransaction();
            if (dlg.isServer()) { // dialog was created on server side
               origMsg = firstTransaction.getOriginalRequest();
               isServer = true;
           } else { // dialog was created on client side
               origMsg = firstTransaction.getLastResponse();
           }
        } else { // out of dialog
            // IMPL_NOTE: implement a support for the case when Contact
            // header is not present.
            if (this instanceof ServerTransaction) {
                origMsg = getOriginalRequest();
                isServer = true;
            }
        }

        if (origMsg == null) {
            // Avoid NPE in case when sending ACK from the part that
            // initiated a Re-INVITE.
            return;
        }

        RecordRouteList recordRouteList = origMsg.getRecordRouteHeaders();
        ContactList cl = origMsg.getContactHeaders();

        if (cl == null) {
            // Prevent NPE and log an error: Contact header is absent.
            if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
                    "Transaction.buildRouteSet(): Contact must be present!");
            }
            return;
        }

        ContactHeader contact = (ContactHeader)cl.getFirst();

        if (recordRouteList == null) {
            URI remoteTarget = contact.getAddress().getURI();

            // RFC 3261, 12.2.1.1 Generating the Request
            // If the route set is empty, the UAC MUST place the remote
            // target URI into the Request-URI.  The UAC MUST NOT add
            // a Route header field to the request.
            request.setRequestURI(remoteTarget);
        } else {
            request.removeHeader(Header.ROUTE);

            RouteList routeList = new RouteList();
            // start at the end of the list and walk backwards
            Vector li = recordRouteList.getHeaders();

            int recSize = li.size();
            for (int i = 0; i < recSize; i++) {
                int j = i;
                if (!isServer) { // on client side the order is reversed
                    j = recSize - i - 1;
                }
                RecordRouteHeader rr = (RecordRouteHeader) li.elementAt(j);
                Address addr = rr.getAddress();
                RouteHeader route = new RouteHeader();
                route.setAddress((Address)rr.getAddress().clone());
                route.setParameters((NameValueList)rr.getParameters().clone());
                routeList.add(route);
            }

            RouteHeader firstRoute = (RouteHeader) routeList.getFirst();

            if (!((SipURI)firstRoute.getAddress().getURI()).hasLrParam()) {
                RouteHeader route = new RouteHeader();
                route.setAddress((Address)contact.getAddress().clone());
                routeList.removeFirst();
                // IMPL_NOTE: is clone() need?
                URI uri = firstRoute.getAddress().getURI();
                request.setRequestURI(uri);
                routeList.add(route);
                request.addHeader(routeList);
            } else {
                URI uri = (URI) contact.getAddress().getURI().clone();
                request.setRequestURI(uri);
                request.addHeader(routeList);
            }
        }
    }
}