/*
* Portions Copyright 2000-2009 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.message;
import gov.nist.siplite.header.*;
import gov.nist.siplite.parser.*;
import gov.nist.siplite.*;
import java.util.*;
import gov.nist.core.*;
import java.io.UnsupportedEncodingException;
import javax.microedition.sip.SipException;
import javax.microedition.sip.SipErrorListener;
import com.sun.j2me.log.Logging;
import com.sun.j2me.log.LogChannels;
/**
* This is the main SIP Message structure.
*
* @see StringMsgParser
* @see PipelinedMsgParser
*
*
* @version JAIN-SIP-1.1
*
*
*<a href="{@docRoot}/uncopyright.html">This code is in the public domain.</a>
*
* IMPL_NOTE: remove 'Vector headers' because its contents is duplicated in nameTable
*/
public abstract class Message extends GenericObject {
/** Class handle. */
private static Class sipHeaderListClass;
/** Default encoding string. */
protected static final String DEFAULT_ENCODING = "UTF-8";
/** Unparsed headers. */
protected Vector unrecognizedHeaders;
/** List of parsed headers (in the order they were added). */
protected Vector headers;
/** From header. */
protected FromHeader fromHeader;
/** To header. */
protected ToHeader toHeader;
/** C sequence header. */
protected CSeqHeader cSeqHeader;
/** Caller identification header. */
protected CallIdHeader callIdHeader;
/** Content length header. */
protected ContentLengthHeader contentLengthHeader;
// protected MaxForwards maxForwardsHeader;
/** Body of message content. */
protected String messageContent;
/** Length of message content in bytes. */
protected byte[] messageContentBytes;
/** Object holding body contents. */
protected Object messageContentObject;
/** Listener to notify about failures of an asynchoronous send operation */
private SipErrorListener sipErrorListener;
static {
try {
sipHeaderListClass = Class.forName
("gov.nist.siplite.header.HeaderList");
} catch (ClassNotFoundException ex) {
InternalErrorHandler.handleException(ex);
}
}
/** Table of headers indexed by name. */
private Hashtable nameTable;
/**
* Sets SipErrorListener for the message.
* Error is notified to SipErrorListener if message was not sent during
* asynchronous operation.
*/
public void setErrorListener(SipErrorListener sel) {
this.sipErrorListener = sel;
}
/**
* Gets SipErrorListener for the message.
* Error is notified to SipErrorListener if message was not sent during
* asynchronous operation.
*/
public SipErrorListener getErrorListener() {
return this.sipErrorListener;
}
/**
* Removes the specified header from "headers" list.
*
* @param headerName expanded name of the header to remove.
*/
private void removeHeaderFromList(String headerName) {
Enumeration li = headers.elements();
int index = -1;
while (li.hasMoreElements()) {
Header sipHeader = (Header) li.nextElement();
index ++;
String currName = NameMap.expandHeaderName(
sipHeader.getName());
if (Utils.equalsIgnoreCase(currName, headerName)) {
break;
}
}
if (index != -1 && index < headers.size()) {
headers.removeElementAt(index);
}
}
/**
* Returns true if the header belongs only in a Request.
*
* @param sipHeader is the header to test.
* @return true if header is part of a request
*/
public static boolean isRequestHeader(Header sipHeader) {
return sipHeader.getHeaderName().equals(Header.ALERT_INFO) ||
sipHeader.getHeaderName().equals(Header.IN_REPLY_TO) ||
sipHeader.getHeaderName().equals(Header.AUTHORIZATION) ||
sipHeader.getHeaderName().equals(Header.MAX_FORWARDS) ||
sipHeader.getHeaderName().equals(Header.PRIORITY) ||
sipHeader.getHeaderName().equals(Header.PROXY_AUTHORIZATION) ||
sipHeader.getHeaderName().equals(Header.PROXY_REQUIRE) ||
sipHeader.getHeaderName().equals(Header.ROUTE) ||
sipHeader.getHeaderName().equals(Header.SUBJECT) ||
sipHeader.getHeaderName().equals(Header.ACCEPT_CONTACT);
}
/**
* Returns true if the header belongs only in a response.
*
* @param sipHeader is the header to test.
* @return true if header is part of a response
*/
public static boolean isResponseHeader(Header sipHeader) {
return sipHeader.getHeaderName().equals(Header.ERROR_INFO) ||
sipHeader.getHeaderName().equals(Header.PROXY_AUTHENTICATE) ||
sipHeader.getHeaderName().equals(Header.SERVER) ||
sipHeader.getHeaderName().equals(Header.UNSUPPORTED) ||
sipHeader.getHeaderName().equals(Header.RETRY_AFTER) ||
sipHeader.getHeaderName().equals(Header.WARNING) ||
sipHeader.getHeaderName().equals(Header.WWW_AUTHENTICATE);
}
/**
* Gets a dialog identifier.
* Generates a string that can be used as a dialog identifier.
*
* @param isServer is set to true if this is the UAS
* and set to false if this is the UAC
* @return the dialig identifier
*/
public String getDialogId(boolean isServer) {
CallIdHeader cid = (CallIdHeader) this.getCallId();
StringBuffer retval = new StringBuffer(cid.getCallId());
FromHeader from = (FromHeader) this.getFromHeader();
ToHeader to = (ToHeader) this.getTo();
if (! isServer) {
if (to.getTag() != null) {
retval.append(to.getTag());
}
if (from.getTag() != null) {
retval.append(from.getTag());
}
} else {
if (from.getTag() != null) {
retval.append(from.getTag());
}
if (to.getTag() != null) {
retval.append(to.getTag());
}
}
return retval.toString().toLowerCase();
}
/**
* Gets a dialog id given the remote tag.
* @param isServer flag indicating a server request
* @param toTag the target recipient
* @return the dialog identifier
*/
public String getDialogId(boolean isServer, String toTag) {
FromHeader from = (FromHeader) this.getFromHeader();
ToHeader to = (ToHeader) this.getTo();
CallIdHeader cid = (CallIdHeader) this.getCallId();
StringBuffer retval = new StringBuffer(cid.getCallId());
if (! isServer) {
if (toTag != null) {
retval.append(toTag);
}
if (from.getTag() != null) {
retval.append(from.getTag());
}
} else {
if (from.getTag() != null) {
retval.append(from.getTag());
}
if (toTag != null) {
retval.append(toTag);
}
}
return retval.toString().toLowerCase();
}
/**
* Encodes this message as a string. This is more efficient when
* the payload is a string (rather than a binary array of bytes).
* If the payload cannot be encoded as a UTF-8 string then it is
* simply ignored (will not appear in the encoded message).
* @return The Canonical String representation of the message
* (including the canonical string representation of
* the SDP payload if it exists).
*/
public String encode() {
StringBuffer encoding = new StringBuffer();
// Synchronization added because of concurrent modification exception
// noticed by Lamine Brahimi.
synchronized (this.headers) {
Enumeration it = this.headers.elements();
while (it.hasMoreElements()) {
Header siphdr = (Header) it.nextElement();
if (! (siphdr instanceof ContentLengthHeader)) {
encoding.append(siphdr.encode());
}
}
}
// Add the content-length header
if (contentLengthHeader != null) {
encoding.append(contentLengthHeader.encode()).append
(Separators.NEWLINE);
}
if (this.messageContentObject != null) {
String mbody = this.getContent().toString();
encoding.append(mbody);
} else if (this.messageContent != null ||
this.messageContentBytes != null) {
String content = null;
try {
if (messageContent != null) {
content = messageContent;
} else {
content = new String(messageContentBytes,
DEFAULT_ENCODING);
}
} catch (UnsupportedEncodingException ex) {
content = "";
}
encoding.append(content);
}
return encoding.toString();
}
/**
* Encodes the message as a byte array.
* Use this when the message payload is a binary byte array.
*
* @return The Canonical byte array representation of the message
* (including the canonical byte array representation of
* the SDP payload if it exists all in one contiguous byte array).
*
*/
public byte[] encodeAsBytes() {
StringBuffer encoding = new StringBuffer();
Enumeration it = this.headers.elements();
while (it.hasMoreElements()) {
Header siphdr = (Header) it.nextElement();
if (! (siphdr instanceof ContentLengthHeader))
encoding.append(siphdr.encode());
}
byte[] retval = null;
byte[] content = this.getRawContent();
if (content != null) {
encoding.append(Header.CONTENT_LENGTH +
Separators.COLON +
Separators.SP + content.length
+ Separators.NEWLINE);
encoding.append(Separators.NEWLINE);
// Append the content
byte[] msgarray = null;
try {
msgarray = encoding.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException ex) {
InternalErrorHandler.handleException(ex);
}
retval = new byte[msgarray.length + content.length];
System.arraycopy(msgarray, 0, retval, 0, msgarray.length);
System.arraycopy(content, 0, retval, msgarray.
length, content.length);
} else {
// Message content does not exist.
encoding.append(Header.CONTENT_LENGTH +
Separators.COLON + Separators.SP + '0'
+ Separators.NEWLINE);
encoding.append(Separators.NEWLINE);
try {
retval = encoding.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException ex) {
InternalErrorHandler.handleException(ex);
}
}
return retval;
}
/**
* Clones this message (create a new deep physical copy).
* All headers in the message are cloned.
* You can modify the cloned copy without affecting
* the original.
*
* @return A cloned copy of this object.
*/
public Object clone() {
Message retval = null;
try {
retval = (Message) this.getClass().newInstance();
} catch (IllegalAccessException ex) {
InternalErrorHandler.handleException(ex);
} catch (InstantiationException ex) {
InternalErrorHandler.handleException(ex);
}
Enumeration li = headers.elements();
while (li.hasMoreElements()) {
Header sipHeader = (Header) ((Header) li.nextElement()).clone();
try {
retval.attachHeader(sipHeader);
} catch (SipException ex) {
if (Logging.REPORT_LEVEL <= Logging.ERROR) {
Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
"Message.clone(): can't attach header '" +
sipHeader.getName() + "'.");
ex.printStackTrace();
}
}
}
if (retval instanceof Request) {
Request thisRequest = (Request) this;
RequestLine rl = (RequestLine)
(thisRequest.getRequestLine()).clone();
((Request) retval).setRequestLine(rl);
} else {
Response thisResponse = (Response) this;
StatusLine sl = (StatusLine)
(thisResponse.getStatusLine()).clone();
((Response) retval).setStatusLine(sl);
}
if (getContent() != null) {
try {
retval.setContent(getContent(), getContentTypeHeader());
} catch (SipException ex) { // Ignore
if (Logging.REPORT_LEVEL <= Logging.ERROR) {
Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
"Message.clone(): can't set the content!");
ex.printStackTrace();
}
}
}
return retval;
}
/**
*
* Constructor: Initializes lists and list headers.
* All the headers for which there can be multiple occurances in
* a message are derived from the HeaderListClass. All singleton
* headers are derived from Header class.
*
*/
public Message() {
unrecognizedHeaders = new Vector();
headers = new Vector();
nameTable = new Hashtable();
}
/**
* Attaches a header and dies if you get a duplicate header exception.
* @param h Header to attach.
* @throws IllegalArgumentException if the header to attach is null.
* @throws SipException if the header can't be attached for some reason.
*/
private void attachHeader(Header h)
throws IllegalArgumentException, SipException {
if (h == null)
throw new IllegalArgumentException("null header!");
if (h instanceof HeaderList) {
HeaderList hl = (HeaderList) h;
if (hl.isEmpty()) {
// System.out.println("Attaching an empty header: " +
// h.getClass().getName());
return;
}
}
attachHeader(h, false, false);
}
/**
* Attaches a header (replacing the original header).
* @param header Header that replaces a header of the same type.
* @throws IllegalArgumentException if the header to add is null.
* @throws SipException if the header can't be set for some reason.
*/
public void setHeader(Header header)
throws IllegalArgumentException, SipException {
if (header == null)
throw new IllegalArgumentException("null header!");
if (header instanceof HeaderList) {
HeaderList hl = (HeaderList) header;
// Ignore empty lists.
if (hl.isEmpty())
return;
}
attachHeader(header, true, false);
}
/**
* Sets a header from a linked list of headers.
*
* @param headers -- a list of headers to set.
* @throws SipException if some header can't be set for some reason.
*/
public void setHeaders(Vector headers) throws SipException {
Enumeration elements = headers.elements();
while (elements.hasMoreElements()) {
Header sipHeader = (Header) elements.nextElement();
attachHeader(sipHeader, false);
}
}
/**
* Attaches a header to the end of the existing headers in
* this Message structure.
* This is equivalent to the attachHeader(Header,replaceflag,false);
* which is the normal way in which headers are attached.
* This was added in support of JAIN-SIP.
*
* @since 1.0 (made this public)
* @param h header to attach.
* @param replaceflag if true then replace a header if it exists.
* @throws SipException if the header can't be attached for some reason.
*/
public void attachHeader(Header h, boolean replaceflag)
throws SipException {
attachHeader(h, replaceflag, false);
}
/**
* Attaches the header to the SIP Message structure at a specified
* position in its list of headers.
*
* @param header Header to attach.
* @param replaceFlag If true then replace the existing header.
* @param top flag to indicate attaching header to the front of list.
* @throws SipException if the header can't be attached for some reason.
*/
public void attachHeader(Header header, boolean replaceFlag, boolean top)
throws SipException {
if (header == null) {
throw new NullPointerException("null header");
}
// System.out.println(">>> attachHeader( " +
// header + ", " + replaceFlag + ");");
Header h;
String expandedHeaderName = NameMap.expandHeaderName(
header.getHeaderName()).toLowerCase();
if (ListMap.hasList(header) &&
! sipHeaderListClass.isAssignableFrom(header.getClass())) {
HeaderList hdrList = ListMap.getList(header);
// Actually, hdrList.size() is always 0.
if (replaceFlag && (hdrList.size() > 0)) {
// remove first element
hdrList.removeElement(hdrList.elementAt(0));
}
hdrList.add(header);
h = hdrList;
} else {
h = header;
}
if (!replaceFlag && nameTable.containsKey(expandedHeaderName) &&
!(h instanceof HeaderList)) {
// Throw an exception here because according to JSR180:
// "The implementations MAY restrict the access to some
// headers according to RFC 3261."
//
// It may happen if this function is called to add a header that
// already exist and the only one header of this type may present
// (Call-Id, From, To).
//
throw new SipException("Header '" + header.getHeaderName() +
"' already exist. Only one header of this type is allowed.",
SipException.INVALID_OPERATION);
}
// Delete the first header with name = headerName
// from our list structure.
// If case of HeaderList the whole list is removed
// to avoid duplication (it is added bellow).
if (replaceFlag || (h instanceof HeaderList)) {
Enumeration li = headers.elements();
int index;
for (index = 0; li.hasMoreElements(); index++) {
Header next = (Header) li.nextElement();
String currName = NameMap.expandHeaderName(
next.getHeaderName());
if (expandedHeaderName.equalsIgnoreCase(currName)) {
headers.removeElementAt(index);
break;
}
}
}
Header hRef = h;
if (h instanceof HeaderList) {
HeaderList hdrlist = (HeaderList) nameTable.get(expandedHeaderName);
if (hdrlist != null) {
if (replaceFlag) {
hdrlist.removeFirst();
}
hdrlist.concatenate((HeaderList)h, top);
// This is required due to the way that 'concatenate'
// is implemented: if 'top' is false, it modifies
// the objects itself; otherwise, the list passed
// as the first parameter is modified.
if (!top) {
hRef = hdrlist;
}
}
}
nameTable.put(expandedHeaderName, hRef);
headers.addElement(hRef);
// Direct accessor fields for frequently accessed headers.
if (h instanceof FromHeader) {
this.fromHeader = (FromHeader)h;
} else if (h instanceof ContentLengthHeader) {
this.contentLengthHeader = (ContentLengthHeader) h;
} else if (h instanceof ToHeader) {
this.toHeader = (ToHeader)h;
} else if (h instanceof CSeqHeader) {
this.cSeqHeader = (CSeqHeader) h;
} else if (h instanceof CallIdHeader) {
this.callIdHeader = (CallIdHeader) h;
}
}
/**
* Removes a header given its name. If multiple headers of a given name
* are present then the top flag determines which end to remove headers
* from.
*
* @param headerName is the name of the header to remove.
* @param top flag that indicates which end of header list to process.
*/
public void removeHeader(String headerName, boolean top) {
// System.out.println("removeHeader " + headerName);
headerName = NameMap.expandHeaderName(headerName).toLowerCase();
Header toRemove = (Header) nameTable.get(headerName);
// nothing to do then we are done.
if (toRemove == null)
return;
if (toRemove instanceof HeaderList) {
HeaderList hdrList = (HeaderList) toRemove;
if (top) hdrList.removeFirst();
else hdrList.removeLast();
// Clean up empty list
if (hdrList.isEmpty()) {
removeHeaderFromList(headerName);
}
} else {
nameTable.remove(headerName);
if (toRemove instanceof FromHeader) {
this.fromHeader = null;
} else if (toRemove instanceof ToHeader) {
this.toHeader = null;
} else if (toRemove instanceof CSeqHeader) {
this.cSeqHeader = null;
} else if (toRemove instanceof CallIdHeader) {
this.callIdHeader = null;
} else if (toRemove instanceof ContentLengthHeader) {
this.contentLengthHeader = null;
}
removeHeaderFromList(headerName);
}
}
/**
* Removes all headers given its name.
*
* @param headerName is the name of the header to remove.
*/
public void removeHeader(String headerName) {
if (headerName == null) {
throw new NullPointerException("null arg");
}
headerName = NameMap.expandHeaderName(headerName).toLowerCase();
Header toRemove = (Header) nameTable.get(headerName);
// nothing to do then we are done.
if (toRemove == null)
return;
nameTable.remove(headerName);
// Remove the fast accessor fields.
if (toRemove instanceof FromHeader) {
this.fromHeader = null;
} else if (toRemove instanceof ToHeader) {
this.toHeader = null;
} else if (toRemove instanceof CSeqHeader) {
this.cSeqHeader = null;
} else if (toRemove instanceof CallIdHeader) {
this.callIdHeader = null;
} else if (toRemove instanceof ContentLengthHeader) {
this.contentLengthHeader = null;
}
removeHeaderFromList(headerName);
}
/**
* Generates (compute) a transaction ID for this SIP message.
* @return A string containing the concatenation of various
* portions of the FromHeader,To,Via and RequestURI portions
* of this message as specified in RFC 2543:
* All responses to a request contain the same values in
* the Call-ID, CSeqHeader, To, and FromHeader fields
* (with the possible addition of a tag in the To field
* (section 10.43)). This allows responses to be matched with requests.
* Incorporates a fix
* for generating transactionIDs when no port is present in the
* via header.
* Incorporates a fix
* (converts to lower case when returning the
* transaction identifier).
*
* @return a string that can be used as a transaction identifier
* for this message. This can be used for matching responses and
* requests (i.e. an outgoing request and its matching response have
* the same computed transaction identifier).
*/
public String getTransactionId() {
ViaHeader topVia = null;
if (! this.getViaHeaders().isEmpty()) {
topVia = (ViaHeader) this.getViaHeaders().first();
}
// Have specified a branch Identifier so we can use it to identify
// the transaction.
if (topVia.getBranch() != null &&
topVia.getBranch().startsWith
(SIPConstants.GENERAL_BRANCH_MAGIC_COOKIE)) {
// Bis 09 compatible branch assignment algorithm.
// implies that the branch id can be used as a transaction
// identifier.
return topVia.getBranch().toLowerCase();
} else {
// Old style client so construct the transaction identifier
// from various fields of the request.
StringBuffer retval = new StringBuffer();
FromHeader from = (FromHeader) this.getFromHeader();
ToHeader to = (ToHeader) this.getTo();
String hpFromHeader = from.getUserAtHostPort();
retval.append(hpFromHeader).append(":");
if (from.hasTag()) retval.append(from.getTag()).append(":");
String hpTo = to.getUserAtHostPort();
retval.append(hpTo).append(":");
String cid = this.callIdHeader.getCallId();
retval.append(cid).append(":");
retval.append(this.cSeqHeader.getSequenceNumber()).append(":").
append(this.cSeqHeader.getMethod());
if (topVia != null) {
retval.append(":").append(topVia.getSentBy().encode());
if (!topVia.getSentBy().hasPort()) {
retval.append(":").append(5060);
}
}
String hc =
Utils.toHexString(retval.toString().toLowerCase().getBytes());
if (hc.length() < 32)
return hc;
else return hc.substring(hc.length() - 32, hc.length() -1);
}
// Convert to lower case
}
/**
* Returns true if this message has a body.
* @return true if message body included
*/
public boolean hasContent() {
return messageContent != null || messageContentBytes != null;
}
/**
* Returns an iterator for the list of headers in this message.
* @return an Iterator for the headers of this message.
*/
public Enumeration getHeaders() {
return headers.elements();
}
/**
* Gets the first header of the given name.
* @param headerName requested header
* @return header the first header of the given name.
*/
public Header getHeader(String headerName) {
if (headerName == null)
throw new NullPointerException("bad name");
headerName = NameMap.expandHeaderName(headerName).toLowerCase();
Header sipHeader = (Header)nameTable.get(headerName);
if (sipHeader == null) {
return null;
}
if (sipHeader instanceof HeaderList) {
return (Header) ((HeaderList) sipHeader).getFirst();
} else {
return (Header) sipHeader;
}
}
/**
* Gets the contentType header (null if one does not exist).
* @return contentType header
*/
public ContentTypeHeader getContentTypeHeader() {
return (ContentTypeHeader) getHeader(Header.CONTENT_TYPE);
}
/**
* Gets the from header.
* @return the from header.
*/
public FromHeader getFromHeader() {
return (FromHeader) fromHeader;
}
/**
* Gets the Contact list of headers (null if one does not exist).
* @return List containing Contact headers.
*/
public ContactList getContactHeaders() {
return (ContactList) getHeaderList(Header.CONTACT);
}
/**
* Gets the Via list of headers (null if one does not exist).
* @return List containing Via headers.
*/
public ViaList getViaHeaders() {
return (ViaList) getHeaderList(Header.VIA);
}
/**
* Gets an iterator to the list of vial headers.
* @return a list iterator to the list of via headers.
* public ListIterator getVia() {
* return this.viaHeaders.listIterator();
* }
*/
/**
* Sets a list of via headers.
* @param viaList a list of via headers to add.
* @throws SipException if the header can't be set for some reason.
*/
public void setVia(ViaList viaList) throws SipException {
setHeader(viaList);
}
/**
* Sets a list of via headers.
* @param viaList a list of via headers to add.
* @throws SipException if the header can't be set for some reason.
*/
public void setVia(Vector viaList) throws SipException {
this.removeHeader(ViaHeader.NAME);
for (int i = 0; i < viaList.size(); i++) {
ViaHeader via = (ViaHeader) viaList.elementAt(i);
this.addHeader(via);
}
}
/**
* Sets the header given a list of headers.
*
* @param sipHeaderList a headerList to set
* @throws SipException if the header can't be set for some reason.
*/
public void setHeader(HeaderList sipHeaderList) throws SipException {
setHeader((Header)sipHeaderList);
}
/**
* Gets the topmost via header.
* @return the top most via header if one exists or null if none exists.
* @throws SipException if the header can't be set for some reason.
*/
public ViaHeader getTopmostVia() {
if (getViaHeaders() == null)
return null;
else
return (ViaHeader) (getViaHeaders().getFirst());
}
/**
* Gets the CSeqHeader list of header (null if one does not exist).
* @return CSeqHeader header
*/
public CSeqHeader getCSeqHeader() {
return cSeqHeader; }
/**
* Gets the sequence number.
* @return the sequence number.
*/
public int getCSeqHeaderNumber() {
return cSeqHeader.getSequenceNumber();
}
/**
* Gets the Route List of headers (null if one does not exist).
* @return List containing Route headers
*/
public RouteList getRouteHeaders() {
return (RouteList) getHeaderList(Header.ROUTE);
}
/**
* Gets the CallIdHeader header (null if one does not exist).
*
* @return Call-ID header.
*/
public CallIdHeader getCallId() {
return callIdHeader;
}
/**
* Sets the call id header.
*
* @param callId call idHeader (what else could it be?)
* @throws SipException if the header can't be set for some reason.
*/
public void setCallId(CallIdHeader callId) throws SipException {
setHeader(callId);
}
/**
* Gets the CallIdHeader header (null if one does not exist)
*
* @param callId -- the call identifier to be assigned to the call id header
* @throws SipException if the header can't be set for some reason.
*/
public void setCallId(String callId)
throws ParseException, SipException {
if (callIdHeader == null) {
setHeader(new CallIdHeader());
}
callIdHeader.setCallId(callId);
}
/**
* Gets the call ID string.
* A conveniance function that returns the stuff following
* the header name for the call id header.
*
* @return the call identifier.
*
*/
public String getCallIdentifier() {
return callIdHeader.getCallId();
}
/**
* Gets the RecordRoute header list (null if one does not exist).
*
* @return Record-Route header
*/
public RecordRouteList getRecordRouteHeaders() {
return (RecordRouteList) this.getHeaderList(Header.RECORD_ROUTE); }
/**
* Gets the To header (null if one does not exist).
* @return To header
*/
public ToHeader getTo() {
return (ToHeader) toHeader; }
/**
* Sets the To header field value.
* @param to the new To field value
* @throws SipException if the header can't be set for some reason.
*/
public void setTo(ToHeader to) throws SipException {
setHeader(to);
}
/**
* Sets the From header field value.
* @param from the new From header field value.
* @throws SipException if the header can't be set for some reason.
*/
public void setFromHeader(FromHeader from) throws SipException {
setHeader(from);
}
/**
* Gets the ContentLengthHeader header (null if one does not exist).
*
* @return content-length header.
*/
public ContentLengthHeader getContentLengthHeader() {
return contentLengthHeader;
}
/**
* Gets the message body as a string.
* If the message contains a content type header with a specified
* charset, and if the payload has been read as a byte array, then
* it is returned encoded into this charset.
*
* @return Message body (as a string)
*/
public String getMessageContent() throws UnsupportedEncodingException {
if (this.messageContent == null && this.messageContentBytes == null)
return null;
else if (this.messageContent == null) {
ContentTypeHeader contentTypeHeader =
(ContentTypeHeader) this.nameTable
.get(Header.CONTENT_TYPE.toLowerCase());
if (contentTypeHeader != null) {
String charset = contentTypeHeader.getCharset();
if (charset != null) {
this.messageContent =
new String(messageContentBytes, charset);
} else {
this.messageContent =
new String(messageContentBytes, DEFAULT_ENCODING);
}
} else this.messageContent =
new String(messageContentBytes, DEFAULT_ENCODING);
}
return this.messageContent;
}
/**
* Gets the message content as an array of bytes.
* If the payload has been read as a String then it is decoded using
* the charset specified in the content type header if it exists.
* Otherwise, it is encoded using the default encoding which is
* UTF-8.
*
* @return an array of bytes that is the message payload.
*
*/
public byte[] getRawContent() {
try {
if (this.messageContent == null &&
this.messageContentBytes == null &&
this.messageContentObject == null) {
return null;
} else if (this.messageContentObject != null) {
String messageContent = this.messageContentObject.toString();
byte[] messageContentBytes;
ContentTypeHeader contentTypeHeader =
(ContentTypeHeader)this.nameTable.get
(Header.CONTENT_TYPE.toLowerCase());
if (contentTypeHeader != null) {
String charset = contentTypeHeader.getCharset();
if (charset != null) {
messageContentBytes = messageContent.getBytes(charset);
} else {
messageContentBytes =
messageContent.getBytes(DEFAULT_ENCODING);
}
} else messageContentBytes =
messageContent.getBytes(DEFAULT_ENCODING);
return messageContentBytes;
} else if (this.messageContent != null) {
byte[] messageContentBytes;
ContentTypeHeader contentTypeHeader =
(ContentTypeHeader)this.nameTable.get
(Header.CONTENT_TYPE.toLowerCase());
if (contentTypeHeader != null) {
String charset = contentTypeHeader.getCharset();
if (charset != null) {
messageContentBytes =
this.messageContent.getBytes(charset);
} else {
messageContentBytes =
this.messageContent.getBytes(DEFAULT_ENCODING);
}
} else messageContentBytes =
this.messageContent.getBytes(DEFAULT_ENCODING);
return messageContentBytes;
} else {
return messageContentBytes;
}
} catch (UnsupportedEncodingException ex) {
InternalErrorHandler.handleException(ex);
return null;
}
}
/**
* Sets the message content given type and subtype.
*
* @param type is the message type (eg. application)
* @param subType is the message sybtype (eg. sdp)
* @param messageContent is the message content as a string.
* @throws IllegalArgumentException if some parameter is invalid.
*/
public void setMessageContent(String type, String subType,
String messageContent)
throws IllegalArgumentException {
if (messageContent == null) {
throw new IllegalArgumentException("messgeContent is null");
}
ContentTypeHeader ct = new ContentTypeHeader(type, subType);
try {
setHeader(ct);
} catch (SipException se) {
throw new IllegalArgumentException(se.getMessage());
}
messageContent = messageContent;
messageContentBytes = null;
messageContentObject = null;
ContentLengthHeader h = getContentLengthHeader();
if (h != null) {
h.setContentLength(messageContent.length());
}
}
/**
* Sets the message content after converting the given object to a
* String.
*
* @param content content to set.
* @param contentTypeHeader content type header corresponding to
* content.
* @throws NullPointerException if the 'content' parameter is null.
* @throws SipException if the content can't be set for some reason.
*/
public void setContent(Object content, ContentTypeHeader contentTypeHeader)
throws SipException {
if (content == null) {
throw new NullPointerException("null content");
}
String contentString = content.toString();
this.setMessageContent(contentString);
this.setHeader(contentTypeHeader);
this.removeContent();
if (content instanceof String) {
this.messageContent = (String)content;
} else if (content instanceof byte[]) {
this.messageContentBytes = (byte[]) content;
} else this.messageContentObject = content;
int length = -1;
if (content instanceof String)
length = ((String)content).length();
else if (content instanceof byte[])
length = ((byte[])content).length;
ContentLengthHeader h = getContentLengthHeader();
if (length != -1 && h != null) {
h.setContentLength(length);
}
}
/**
* Sets the message content after converting the given object to a
* String.
*
* @param content content to set.
* @throws NullPointerException if the content parameter is null.
*/
public void setContent(Object content) {
if (content == null)
throw new NullPointerException("null content");
String contentString = content.toString();
this.setMessageContent(contentString);
this.removeContent();
if (content instanceof String) {
this.messageContent = (String)content;
} else if (content instanceof byte[]) {
this.messageContentBytes = (byte[]) content;
} else this.messageContentObject = content;
int length = -1;
if (content instanceof String)
length = ((String)content).length();
else if (content instanceof byte[])
length = ((byte[])content).length;
ContentLengthHeader h = getContentLengthHeader();
if (length != -1 && h != null) {
h.setContentLength(length);
}
}
/**
* Gets the content of the header.
*
* @return the content of the sip message.
*/
public Object getContent() {
if (this.messageContentObject != null)
return messageContentObject;
else if (this.messageContentBytes != null)
return this.messageContentBytes;
else if (this.messageContent != null)
return this.messageContent;
else return null;
}
/**
* Sets the message content for a given type and subtype.
*
* @param type is the messge type.
* @param subType is the message subType.
* @param messageContent is the message content as a byte array.
* @throws SipException if the message content can't be set.
*/
public void setMessageContent(String type, String subType,
byte[] messageContent) throws SipException {
ContentTypeHeader ct = new ContentTypeHeader(type, subType);
setHeader(ct);
setMessageContent(messageContent);
ContentLengthHeader h = getContentLengthHeader();
if (h != null) {
h.setContentLength(messageContent.length);
}
}
/**
* Sets the message content for this message.
*
* @param content Message body as a string.
*/
public void setMessageContent(String content) {
ContentLengthHeader h = getContentLengthHeader();
if (h != null) {
int clength = (content == null ? 0: content.length());
h.setContentLength(clength);
}
messageContent = content;
messageContentBytes = null;
messageContentObject = null;
}
/**
* Sets the message content as an array of bytes.
*
* @param content is the content of the message as an array of bytes.
*/
public void setMessageContent(byte[] content) {
ContentLengthHeader h = getContentLengthHeader();
if (h != null) {
h.setContentLength(content.length);
}
messageContentBytes = content;
messageContent = null;
messageContentObject = null;
}
/**
* Removes the message content if it exists.
*/
public void removeContent() {
messageContent = null;
messageContentBytes = null;
messageContentObject = null;
}
/**
* Gets a SIP header or Header list given its name.
* @param headerName is the name of the header to get.
* @return a header or header list that contians the retrieved header.
*/
public Enumeration getHeaders(String headerName) {
if (headerName == null) {
throw new NullPointerException("null headerName");
}
Header sipHeader = (Header)nameTable.get(
NameMap.expandHeaderName(headerName).toLowerCase());
// empty iterator
if (sipHeader == null) {
return new Vector().elements();
}
if (sipHeader instanceof HeaderList) {
return ((HeaderList) sipHeader).getElements();
} else {
Vector v = new Vector();
v.addElement(sipHeader);
return v.elements();
}
}
/**
* Gets a SIP Header list given its name.
* @param headerName is the name of the header to get.
* @return a header list that contains the retrieved header.
*/
public HeaderList getHeaderList(String headerName) {
Header header = (Header)nameTable.get(
NameMap.expandHeaderName(headerName).toLowerCase());
if (header == null) {
return null;
}
if (header instanceof HeaderList) {
return (HeaderList)header;
} else {
HeaderList hl = new HeaderList();
hl.add(header);
return hl;
}
}
/**
* Returns true if the Message has a header of the given name.
*
* @param headerName is the header name for which we are testing.
* @return true if the header is present in the message
*/
public boolean hasHeader(String headerName) {
return nameTable.containsKey(
NameMap.expandHeaderName(headerName).toLowerCase());
}
/**
* Returns true if the message has a FromHeader header tag.
*
* @return true if the message has a from header and that header has
* a tag.
*/
public boolean hasFromHeaderTag() {
return fromHeader != null && fromHeader.getTag() != null;
}
/**
* Returns true if the message has a To header tag.
*
* @return true if the message has a to header and that header has
* a tag.
*/
public boolean hasToTag() {
return toHeader != null && toHeader.getTag() != null;
}
/**
* Returns the from tag.
*
* @return the tag from the from header.
*
*/
public String getFromHeaderTag() {
return fromHeader == null? null: fromHeader.getTag();
}
/**
* Sets the FromHeader Tag.
*
* @param tag -- tag to set in the from header.
* @throws SipException if the header can't be set for some reason.
*/
public void setFromHeaderTag(String tag) throws SipException {
fromHeader.setTag(tag);
}
/**
* Sets the to tag.
*
* @param tag -- tag to set.
* @throws SipException if the header can't be set for some reason.
*/
public void setToTag(String tag) throws SipException {
toHeader.setTag(tag);
}
/**
* Returns the To tag.
* @return the To tag field
*/
public String getToTag() {
return toHeader == null ? null : toHeader.getTag();
}
/**
* Returns the encoded first line.
* @return the first line
*/
public abstract String getFirstLine();
/**
* Adds a SIP header.
* @param sipHeader -- sip header to add.
* @throws SipException if the header can't be added for some reason.
*/
public void addHeader(Header sipHeader) throws SipException {
Header sh = (Header) sipHeader;
// Add the header value topmost of this type of headers
// as required by JSR180.
attachHeader(sh, false, true);
/*
if (sipHeader instanceof ViaHeader) {
attachHeader(sh, false, true);
} else {
attachHeader(sh, false, false);
}
*/
}
/**
* Adds a header to the unparsed list of headers.
*
* @param unparsed -- unparsed header to add to the list.
*/
public void addUnparsed(String unparsed) {
unrecognizedHeaders.addElement(unparsed);
}
/**
* Adds a SIP header.
* @param sipHeader -- string version of SIP header to add.
* @throws SipException if the header can't be added for some reason.
*/
public void addHeader(String sipHeader) throws SipException {
String hdrString = sipHeader.trim() + "\n";
try {
HeaderParser parser = ParserFactory.createParser(sipHeader);
Header sh = parser.parse();
attachHeader(sh, false);
} catch (ParseException ex) {
unrecognizedHeaders.addElement(hdrString);
}
}
/**
* Gets a list containing the unrecognized headers.
* @return a linked list containing unrecongnized headers.
*/
public Enumeration getUnrecognizedHeaders() {
return unrecognizedHeaders.elements();
}
/**
* Gets the header names.
*
* @return a list iterator to a list of header names. These are ordered
* in the same order as are present in the message.
*/
public Enumeration getHeaderNames() {
Enumeration li = this.headers.elements();
Vector retval = new Vector();
while (li.hasMoreElements()) {
Header sipHeader = (Header) li.nextElement();
String name = sipHeader.getName();
retval.addElement(name);
}
return retval.elements();
}
/**
* Compares for equality.
*
* @param other the other object to compare with.
* @return true if object matches
*/
public boolean equals(Object other) {
if (!other.getClass().equals(this.getClass()))
return false;
Message otherMessage = (Message) other;
Enumeration values = this.nameTable.elements();
Hashtable otherNameTable = otherMessage.nameTable;
if (otherNameTable.size() != nameTable.size())
return false;
while (values.hasMoreElements()) {
Header mine = (Header) values.nextElement();
// maybe short form
String mineName =
NameMap.expandHeaderName(mine.getHeaderName()).trim()
.toLowerCase();
Header hisHeader = (Header) otherNameTable.get(mineName);
String his = null;
if (hisHeader != null) {
his = hisHeader.toString().trim();
}
if (his == null) {
return false;
} else if (! his.equals(mine.toString().trim())) {
return false;
}
}
return true;
}
/**
* Sets the content length header.
*
* @param contentLength content length header.
* @throws SipException if Content-Length header can't be set
* for some reason.
*/
public void setContentLength(ContentLengthHeader contentLength)
throws SipException {
setHeader(contentLength);
}
/**
* Sets the CSeqHeader header.
*
* @param cseqHeader CSeqHeader Header.
* @throws SipException if CSeq header can't be added for some reason.
*/
public void setCSeqHeader(CSeqHeader cseqHeader) throws SipException {
setHeader(cseqHeader);
}
/**
* Sets the SIP version header.
* @param sipVersion the new version string
*/
public abstract void setSIPVersion(String sipVersion) throws ParseException;
/**
* Gets the SIP vesrion string.
* @return the SIP version string
*/
public abstract String getSIPVersion();
}