/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (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/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright (c) Ericsson AB, 2004-2007. All rights reserved.
 */
package com.ericsson.ssa.sip;

import com.ericsson.ssa.container.auth.AuthModule;
import com.ericsson.ssa.dd.ServletMappingCriteria;
import com.ericsson.ssa.sip.PathNode.Type;
import com.ericsson.ssa.sip.dns.TargetTuple;
import com.ericsson.ssa.utils.ParserHelper;
import com.ericsson.ssa.utils.StringDataSource;
import com.ericsson.ssa.utils.StringOutputStream;

import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.UnsupportedEncodingException;

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.security.Principal;
import java.security.cert.X509Certificate;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;

import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.internet.MimeMultipart;

import javax.servlet.sip.Address;
import javax.servlet.sip.Parameterable;
import javax.servlet.sip.ServletParseException;
import javax.servlet.sip.SipApplicationSession;
import javax.servlet.sip.SipServletMessage;
import javax.servlet.sip.SipSession;


/**
 * @author ekrigro TODO To change the template for this generated type comment
 *         go to Window - Preferences - Java - Code Style - Code Templates
 */
public abstract class SipServletMessageImpl implements SipServletMessage,
    ServletMappingCriteria, Externalizable {
    // NOTE! If you ADD | REMOVE check cloneShallowHelper() and update it!
    protected static final EnumerationConverter<String> empty = new EnumerationConverter<String>();

    // Indicates if a message has been sent
    private static ThreadLocal<ThreadData> _tlData = new ThreadLocal<ThreadData>();

    // protected String protocol;
    protected SipFactoryImpl _sf = SipFactoryImpl.getInstance();
    protected SipSessionBase _session = null;
    protected String _method = "";
    protected String _protocol;
    protected String _default_enc = "UTF8";
    protected String _content_enc = _default_enc;
    protected byte[] _content_byte = null;
    protected Object _content_obj = null;
    protected Map<String, Object> attrib = null;

    // Currently no systemAttrib exist today (only be set by container). See SSA.
    protected Map<String, Object> systemAttrib = null;
    protected Map<String, Header> headerMap = new HashMap<String, Header>();
    protected List<Dispatcher> _transactionStack = new ArrayList<Dispatcher>();
    protected List<Dispatcher> _applicationStack = new ArrayList<Dispatcher>();
    protected List<String> _roles = new ArrayList<String>();
    protected int _content_byte_offset = 0;
    protected TargetTuple _remote;
    protected InetSocketAddress _local;
    protected Type _Type = Type.Undefined;
    protected boolean _IsContactIndicated = false;
    protected boolean _headersComplete = false;
    protected boolean _messageComplete = false;
    protected DialogFragment _dialog = null;
    protected int _fragmentId = DialogFragment.DEFAULT_FRAGMENT_ID;
    private Principal _principal = null;
    private String _user = null;
    private X509Certificate[] _clientCert = null;
    private AuthModule _authModule = null;
    protected SipMessageType _messageType;

    public SipServletMessageImpl() {
    }

    public SipServletMessageImpl(String protocol) {
        _protocol = protocol;
    }

    public SipServletMessageImpl(String method, String protocol) {
        _protocol = protocol;
        _method = method;
    }

    public SipMessageType getMessageType() {
        return _messageType;
    }

    public void writeExternal(ObjectOutput output) throws IOException {
        try {
            // _method, _protocol, _content_byte, headerMap, _remote, _local
            output.writeUTF(_method); // TODO change to writeInt + writeBytes and
                                      // check performance

            output.writeUTF(_protocol);

            if (_content_byte != null) {
                output.writeInt(_content_byte.length);
                output.write(_content_byte);
            } else {
                output.writeInt(0);
            }

            output.writeInt(headerMap.size());

            Iterator<String> key = headerMap.keySet().iterator();
            Iterator<Header> value = headerMap.values().iterator(); // TODO check
                                                                    // the Header
                                                                    // serialization

            while (key.hasNext()) {
                output.writeUTF(key.next());
                output.writeObject(value.next());
            }

            output.writeObject(_remote); // TODO check the TargetTuple
                                         // serialization

            output.writeObject(_local); // Is the InetSocketAddress serialization
                                        // fast!?
                                        // TODO _IsContactIndicated, _headersComplete, _messageComplete
                                        // output.writeBoolean(IsContactIndicated)
        } catch (Exception ignore) {
        }
    }

    public void readExternal(ObjectInput input) throws IOException {
        try {
            // method, _protocol, _content_byte, headerMap, _remote, _local
            _method = input.readUTF();
            _protocol = input.readUTF();

            int cbl = input.readInt();
            _content_byte = new byte[cbl];
            input.read(_content_byte);

            int hml = input.readInt();

            if (headerMap == null) {
                headerMap = new HashMap<String, Header>(hml);
            }

            for (int i = 0; i < hml; i++) {
                String key = input.readUTF();
                Header value = (Header) input.readObject();
                headerMap.put(key, value);
            }

            _remote = (TargetTuple) input.readObject();
            _local = (InetSocketAddress) input.readObject();
            _transactionStack = new ArrayList<Dispatcher>();
            _applicationStack = new ArrayList<Dispatcher>();
            // TODO - maybe not neede since we dispatch it on new thread in the
            // SerialManager
            _tlData = new ThreadLocal<ThreadData>();
            _sf = SipFactoryImpl.getInstance();
        } catch (Exception ignore) {
        }
    }

    /**
     * The message coming from caller (Type.Caller) or callee (Type.Callee). If
     * its not determined it could also be Type.Undefined
     *
     * @return the direction of the message. Coming from caller or callee.
     */
    public Type isDirection() {
        if ((_Type != Type.Caller) && (_Type != Type.Callee)) {
            try {
                if (getDialog() != null) {
                    if (getDialog().isMessageFromCaller(this)) {
                        _Type = Type.Caller;
                    } else {
                        _Type = Type.Callee;
                    }
                } else {
                    return Type.Undefined;
                }
            } catch (NotEqualDialogException e) {
                return Type.Undefined;
            }
        }

        return _Type;
    }

    public void setDirection(Type direction) {
        _Type = direction;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getFrom()
     */
    public Address getFrom() {
        try {
            return headerMap.get(Header.FROM).getAddressValue();
        } catch (ServletParseException e) {
            throw new LazyParsingException(400, e.getMessage(), e);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getTo()
     */
    public Address getTo() {
        try {
            return headerMap.get(Header.TO).getAddressValue();
        } catch (ServletParseException e) {
            throw new LazyParsingException(400, e.getMessage(), e);
        }
    }

    public String createTag(String name) {
        String tag = _sf.createTag();

        try {
            Address adr = headerMap.get(name).getAddressValue();
            ((AddressImpl) adr).setReadOnly(false);
            adr.setParameter(AddressImpl.TAG_PARAM, tag);
            ((AddressImpl) adr).setReadOnly(true);
        } catch (ServletParseException e) {
            return null;
        }

        return tag;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getMethod()
     */
    public String getMethod() {
        return _method; // The req and response must set the method.
    }

    // Container internal when the message is parsed
    public void setMethod(String method) {
        _method = method;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getProtocol()
     */
    public String getProtocol() {
        return _protocol;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getHeader(java.lang.String)
     */
    public String getHeader(String name) {
        Header header = headerMap.get(Header.format(name));

        if (header == null) {
            return null;
        }

        return header.getValue();
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getHeaders(java.lang.String)
     */
    public ListIterator<String> getHeaders(String name) {
        String pretty = Header.format(name);

        if (headerMap.containsKey(pretty)) {
            return headerMap.get(pretty).getValues();
        }

        return Collections.EMPTY_LIST.listIterator();
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getHeaderNames()
     */
    public Iterator<String> getHeaderNames() {
        return headerMap.keySet().iterator();
    }

    /*
     * (non-Javadoc) Overwrites
     *
     * @see javax.servlet.sip.SipServletMessage#setHeader(java.lang.String,
     *      java.lang.String)
     */
    public void setHeader(String name, String value) {
        Header header = null;
        String pretty = Header.format(name);
        boolean system = Header.isSystemHeader(pretty, this);
        String crap = "\r\n\t";

        if ((value.indexOf(crap)) > 0) {
            value = value.replaceAll(crap, "");
        }

        if (system) {
            throw new IllegalArgumentException(Header.ILLEGAL_ACCESS);
        }

        if (headerMap.containsKey(pretty)) {
            header = headerMap.get(pretty);
            header.removeValues();
            header.setValue(value, true);
        } else {
            header = Header.createFormated(pretty, this);
            header.setValue(value, true);
            headerMap.put(pretty, header);
        }
    }

    /*
     * (non-Javadoc) adds
     *
     * @see javax.servlet.sip.SipServletMessage#addHeader(java.lang.String,
     *      java.lang.String)
     */
    public void addHeader(String name, String value) {
        Header header = null;
        String pretty = Header.format(name);
        boolean system = Header.isSystemHeader(pretty, this);

        if (system) {
            throw new IllegalArgumentException(Header.ILLEGAL_ACCESS);
        }

        if (headerMap.containsKey(pretty)) {
            header = headerMap.get(pretty);
            header.setValue(value, false);
        } else {
            header = Header.createFormated(pretty, this);
            header.setValue(value, false);
            headerMap.put(pretty, header);
        }
    }

    public void addHeader(Header header) {
        Header in = headerMap.get(header.getName());

        if (in != null) {
            in.merge(header);
        } else {
            headerMap.put(header.getName(), header);
        }
    }

    public void setHeader(Header header) {
        headerMap.put(header.getName(), header);
    }

    /*
     * public String getHeader(Header header) { Header in =
     * headerMap.get(header.getName()); if (in != null) return in.toString();
     * return null; }
     */
    public Header getRawHeader(String header) {
        return headerMap.get(header);
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#removeHeader(java.lang.String)
     */
    public void removeHeader(String name) {
        String pretty = Header.format(name);

        if (Header.isSystemHeader(pretty, this)) {
            throw new IllegalArgumentException(Header.ILLEGAL_ACCESS);
        }

        headerMap.remove(name);
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getAddressHeader(java.lang.String)
     */
    public Address getAddressHeader(String name) throws ServletParseException {
        Header address = headerMap.get(Header.format(name));

        if (address == null) {
            return null;
        }

        return address.getAddressValue();
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getAddressHeaders(java.lang.String)
     */
    public ListIterator<Address> getAddressHeaders(String name)
        throws ServletParseException {
        String pretty = Header.format(name);
        boolean systemHeader = Header.isSystemHeader(pretty, this);

        if (headerMap.containsKey(pretty)) {
            return new SipListIterator(headerMap.get(pretty).getAddressValues(),
                systemHeader);
        } else {
            Header header = Header.createFormated(pretty, this);
            headerMap.put(pretty, header);

            return new SipListIterator(headerMap.get(pretty).getAddressValues(),
                systemHeader);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#setAddressHeader(java.lang.String,
     *      javax.servlet.sip.Address)
     */
    public void setAddressHeader(String name, Address value) {
        Header header = null;
        String pretty = Header.format(name);
        boolean system = Header.isSystemHeader(pretty, this);

        if (system) {
            throw new IllegalArgumentException(Header.ILLEGAL_ACCESS);
        }

        if (headerMap.containsKey(pretty)) {
            header = headerMap.get(pretty);
            header.removeValues();
            header.setAddressValue(value, false);
        } else {
            header = Header.createFormated(pretty, this);
            header.setAddressValue(value, false);
            headerMap.put(pretty, header);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#addAddressHeader(java.lang.String,
     *      javax.servlet.sip.Address, boolean)
     */
    public void addAddressHeader(String name, Address value, boolean first) {
        Header header = null;
        String pretty = Header.format(name);
        boolean system = Header.isSystemHeader(pretty, this);

        if (system) {
            throw new IllegalArgumentException(Header.ILLEGAL_ACCESS);
        }

        if (headerMap.containsKey(pretty)) {
            header = headerMap.get(pretty);
            header.setAddressValue(value, first);
        } else {
            // already formated using createFormated
            header = Header.createFormated(pretty, this);
            header.setAddressValue(value, first);
            headerMap.put(pretty, header);
        }
    }

    public void addParameterableHeader(String name, Parameterable param,
        boolean first) {
        Header header = null;
        String pretty = Header.format(name);
        boolean system = Header.isSystemHeader(pretty, this);

        if (system) {
            throw new IllegalArgumentException(Header.ILLEGAL_ACCESS);
        }

        if (headerMap.containsKey(pretty)) {
            header = headerMap.get(pretty);
            header.setParameterableValue(param, first);
        } else {
            // already formated using createFormated
            header = Header.createFormated(pretty, this);
            header.setParameterableValue(param, first);
            headerMap.put(pretty, header);
        }
       
    }

    public Parameterable getParameterableHeader(String name)
        throws ServletParseException {
        Header parameterable = headerMap.get(Header.format(name));

        if (parameterable == null) {
            return null;
        }

        return parameterable.getParameterableValue();
    
    }

    public ListIterator<Parameterable> getParameterableHeaders(String name)
        throws ServletParseException {
        String pretty = Header.format(name);
        boolean systemHeader = Header.isSystemHeader(pretty, this);

        if (headerMap.containsKey(pretty)) {
            return new SipListIterator(headerMap.get(pretty).getParameterableValues(),
                systemHeader);
        } else {
            Header header = Header.createFormated(pretty, this);
            headerMap.put(pretty, header);
            return new SipListIterator(headerMap.get(pretty).getParameterableValues(),
                systemHeader);
        }
    }

    public void setParameterableHeader(String name, Parameterable param) {
        Header header = null;
        String pretty = Header.format(name);
        boolean system = Header.isSystemHeader(pretty, this);

        if (system) {
            throw new IllegalArgumentException(Header.ILLEGAL_ACCESS);
        }

        if (headerMap.containsKey(pretty)) {
            header = headerMap.get(pretty);
            header.removeValues();
            header.setParameterableValue(param, false);
        } else {
            header = Header.createFormated(pretty, this);
            header.setParameterableValue(param, false);
            headerMap.put(pretty, header);
        }
    }

    /* To be implemented */
    public SipServletMessage.HeaderForm getHeaderForm() {
        return SipServletMessage.HeaderForm.DEFAULT;
    }

    public void setHeaderForm(SipServletMessage.HeaderForm form) {
        throw new RuntimeException(" Not yet implemented ");
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getCallId()
     */
    public String getCallId() {
        return headerMap.get(Header.CALL_ID).getValue();
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getExpires()
     */
    public int getExpires() {
        Header header = headerMap.get(Header.EXPIRES);

        if (header == null) {
            return -1;
        }

        String exp = header.getValue();

        if (exp == null) {
            return -1;
        }

        return Integer.parseInt(exp);
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#setExpires(int)
     */
    public void setExpires(int seconds) {
        // TODO Could be optimized since no pretty print is required in this
        // case
        setHeader(Header.EXPIRES, String.valueOf(seconds));
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getCharacterEncoding()
     */
    public String getCharacterEncoding() {
        Header h = headerMap.get(Header.CONTENT_ENCODING);

        if (h != null) {
            return h.getValue();
        }

        // This method should return null if the message does not specify
        // a character encoding. The service will use UTF-8 as default, SSA1.0.
        Header sh = headerMap.get(Header.CONTENT_TYPE);

        if (sh != null) {
            String value = sh.getValue();

            if (value != null) {
                _content_enc = ParserHelper.getValue(value, "charset", ";");
            }

            if (_content_enc != null) {
                return _content_enc;
            }
        }

        return null;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#setCharacterEncoding(java.lang.String)
     */
    public void setCharacterEncoding(String enc)
        throws UnsupportedEncodingException {
        // TODO see what encodings we should support
        // Perfect thing to write in a SAD
        Header h = headerMap.get(Header.CONTENT_TYPE);

        if (h != null) {
            String newEnc = ParserHelper.replaceParam(h.getValue(), "charset",
                    enc, ";", true);
            setHeader(Header.CONTENT_TYPE, newEnc);
            _content_enc = enc;
        }
    }

    public int getContentLengthHeader() {
        if (headerMap.get(Header.CONTENT_LENGTH) == null) {
            // fix so that SipParser notices when no contentLength is set
            return -1;
        }

        return getContentLength();
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getContentLength()
     */
    public int getContentLength() {
        Header header = headerMap.get(Header.CONTENT_LENGTH);

        if (header == null) {
            return 0;
        }

        String len = header.getValue();

        if (len == null) {
            return 0;
        }

        return Integer.parseInt(len);
    }

    public int getMessageSize() {
        if ((_content_byte == null) && (_content_obj == null)) {
            return 0;
        } else if (_content_byte != null) {
            return _content_byte.length;
        } else if (_content_obj != null) {
            return _content_obj.toString().length();
        }

        return 0;

        // TODO - Add the header size + requri
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getContentType()
     */
    public String getContentType() {
        if ((_content_byte == null) || (_content_byte.length == 0)) {
            return null;
        }

        Header h = headerMap.get(Header.CONTENT_TYPE);

        if (h != null) {
            return h.getValue();
        }

        return null;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getRawContent()
     */
    public byte[] getRawContent() throws IOException {
        byte[] bytes = null;

        if ((_content_byte != null) || (_content_obj != null)) {
            Header sh = headerMap.get(Header.CONTENT_TYPE);

            if (sh != null) {
                String newEnc = ParserHelper.getValue(sh.getValue(), "charset",
                        ";");

                if (newEnc != null) {
                    _content_enc = newEnc;
                }

                if (_content_obj != null) {
                    if (_content_obj instanceof Multipart) {
                        try {
                            ByteArrayOutputStream stream = new ByteArrayOutputStream();
                            ((Multipart) _content_obj).writeTo(stream);
                            bytes = stream.toByteArray();
                            stream.close();
                        } catch (MessagingException e) {
                            throw new IOException(e.getMessage());
                        }
                    } else {
                        String msg = _content_obj.toString();
                        bytes = msg.getBytes(_content_enc);
                    }
                } else if (_content_byte != null) {
                    return _content_byte;
                }
            }
        }

        return bytes;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getContent()
     */
    public Object getContent() throws IOException, UnsupportedEncodingException {
        Object body = null;
        Header h = headerMap.get(Header.CONTENT_TYPE);

        if (h != null) {
            String mediaType = ParserHelper.getFirstToken(h.getValue(), "/");
            String charset = ParserHelper.getValue(h.getValue(), "charset", ";");

            if (_content_byte != null) {
                if (charset == null) {
                    charset = _content_enc;

                    if (charset == null) {
                        charset = _default_enc;
                    }
                }

                // Return a string if it is text
                if ("text".equalsIgnoreCase(mediaType)) {
                    body = getContentAsString(charset);
                }
                // Return a MimeMultipart if we have one
                else if ((_content_obj != null) &&
                        "multipart".equalsIgnoreCase(mediaType) &&
                        (_content_obj instanceof Multipart)) {
                    body = _content_obj;
                }
                // Unknown content, return a byte array
                else {
                    body = getRawContent();
                }
            } else if (_content_obj != null) {
                if ("multipart".equalsIgnoreCase(mediaType) &&
                        (_content_obj instanceof Multipart)) {
                    body = _content_obj;
                }
            }
        }

        return body;
    }

    public Object getContent(Class[] classes)
        throws IOException, UnsupportedEncodingException {
        throw new RuntimeException(" Not yet implemeneted ");
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#setContent(java.lang.Object,
     *      java.lang.String)
     */
    public void setContent(Object content, String contentType)
        throws UnsupportedEncodingException {
        if ((content == null) || (contentType == null)) {
            throw new NullPointerException("null content");
        }

        int contentLength = -1;
        String mediatype = ParserHelper.getFirstToken(contentType, "/");

        if (mediatype == null) {
            throw new UnsupportedEncodingException(
                "media type not known or null");
        }

        if (mediatype.equalsIgnoreCase("text")) {
            if (content instanceof byte[]) {
                _content_byte = (byte[]) content;
                contentLength = _content_byte.length;
            } else if (content instanceof String) {
                String encoding = getCharacterEncoding();

                if (encoding == null) {
                    encoding = _default_enc;
                }

                _content_byte = ((String) content).getBytes(encoding);
                contentLength = _content_byte.length;
            } else {
                _content_obj = content.toString();
                contentLength = _content_obj.toString().length();
            }
        } else if (mediatype.equalsIgnoreCase("multipart")) {
            if (content instanceof Multipart) {
                _content_obj = content;

                StringBuffer buffer = new StringBuffer();
                StringOutputStream stream = new StringOutputStream(buffer);

                try {
                    ((Multipart) _content_obj).writeTo(stream);
                    stream.close();
                    contentLength = buffer.length();
                } catch (IOException e) {
                    throw new IllegalStateException(e.toString());
                } catch (MessagingException e) {
                    throw new IllegalStateException(e.toString());
                }
            } else {
                try {
                    // getCharacterEncoding can return null.
                    String encoding = getCharacterEncoding();

                    if (encoding == null) {
                        encoding = _default_enc;
                    }

                    contentLength = parseMultipart(content, contentType,
                            encoding);
                } catch (MessagingException e) {
                    throw new IllegalStateException(e.getMessage());
                }
            }
        } else if (content instanceof byte[]) {
            _content_byte = (byte[]) content;
            contentLength = _content_byte.length;
        } else if (content instanceof String) {
            String encoding = getCharacterEncoding();

            if (encoding == null) {
                encoding = _default_enc;
            }

            _content_byte = ((String) content).getBytes(encoding);
            contentLength = _content_byte.length;
        } else {
            throw new IllegalArgumentException(
                "Content object is not supported for this MIME type");
        }

        setHeader(Header.CONTENT_TYPE, contentType);

        if (contentLength > -1) {
            setHeader(Header.CONTENT_LENGTH, String.valueOf(contentLength));
        }
    }

    /* Internal container function called by the Parser */
    public void setContent(byte[] content) {
        _content_byte = content;

        try {
            String ctype = getContentType();

            if (ctype != null) {
                setContent(content, ctype);
            }
        } catch (UnsupportedEncodingException e) {
        }
    }

    public int getContentMaxLength() {
        // TODO read value from jmx config
        // TODO define constant if no jmx config
        return 500000;
    }

    public void addContent(byte[] srcBytes, int pos, int length) {
        if (_content_byte == null) {
            // allocate array to hold the hole content (possibly several deltas)
            int content_len = getContentLengthHeader();

            if (content_len > getContentMaxLength()) {
                throw new RuntimeException(
                    "Content-length exceeds defined max value:" +
                    getContentLengthHeader() + "(" + getContentMaxLength() +
                    ")");
            }

            _content_byte = new byte[getContentLengthHeader()];
        }

        System.arraycopy(srcBytes, pos, _content_byte, _content_byte_offset,
            length);
        _content_byte_offset += length;
    }

    /**
     * Internal container function which returns the currrent parsing offset of
     * content data (used for internal debugging purpose)
     */
    public int getContentOffset() {
        return _content_byte_offset;
    }

    /*
     * Internal container function to set "Content-Length" header after
     */
    public void internalSetContentLength(int len) {
        setHeader(Header.CONTENT_LENGTH, new Integer(len).toString());
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#setContentLength(int)
     */
    public void setContentLength(int len) {
        new IllegalStateException("Instead use the setContent method please!");
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#setContentType(java.lang.String)
     */
    public void setContentType(String type) {
        setHeader(Header.CONTENT_TYPE, type); // TODO enc
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getAttribute(java.lang.String)
     */
    public Object getAttribute(String name) {
        if (attrib != null) {
            return attrib.get(name);
        }

        if (systemAttrib != null) {
            return systemAttrib.get(name);
        }

        return null;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getAttributeNames()
     */
    public Enumeration<String> getAttributeNames() {
        if (attrib != null) {
            return new EnumerationConverter<String>(attrib.keySet());
        }

        if (systemAttrib != null) {
            return new EnumerationConverter<String>(systemAttrib.keySet());
        }

        return empty;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#setAttribute(java.lang.String,
     *      java.lang.Object)
     */
    public void setAttribute(String name, Object o) {
        if (attrib == null) {
            if (systemAttrib != null) {
                attrib = new HashMap<String, Object>(systemAttrib);
            } else {
                attrib = new HashMap<String, Object>(1, 1);
            }
        }

        attrib.put(name, o);
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getSession()
     */
    public SipSession getSession() {
        return getSession(true);
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getSession(boolean)
     */
    public SipSession getSession(boolean create) {
        return getSessionImpl();
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getApplicationSession()
     */
    public SipApplicationSession getApplicationSession() {
        return getApplicationSession(true);
    }

    public SipSessionBase getSessionImpl() {
        return _session;
    }

    public void setSession(SipSessionBase sessionImpl) {
        _session = sessionImpl;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getApplicationSession(boolean)
     */
    public SipApplicationSession getApplicationSession(boolean create) {
        return getApplicationSessionImpl();
    }

    public SipApplicationSessionImpl getApplicationSessionImpl() {
        return getSessionImpl().getApplicationSessionImpl();
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getAcceptLanguage()
     */
    public Locale getAcceptLanguage() {
        Header al = headerMap.get(Header.ACCEPT_LANGUAGE);

        if (al != null) {
            String val = al.getValue();

            if ((val != null) && (val.length() == 2)) {
                return (new Locale(val));
            }
        }

        return Locale.getDefault();
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getAcceptLanguages()
     */
    public Iterator<Locale> getAcceptLanguages() {
        ArrayList<Locale> locales = new ArrayList<Locale>();
        Header al = headerMap.get(Header.ACCEPT_LANGUAGE);

        if (al != null) {
            Iterator<String> i = al.getValues();

            while (i.hasNext()) {
                locales.add(new Locale(i.next()));
            }
        } else {
            locales.add(Locale.getDefault());
        }

        return locales.iterator();
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#setAcceptLanguage(java.util.Locale)
     */
    public void setAcceptLanguage(Locale locale) {
        if (locale == null) {
            headerMap.remove(Header.ACCEPT_LANGUAGE);
        } else {
            Header al = headerMap.get(Header.ACCEPT_LANGUAGE);

            if (al == null) {
                al = new MultiLineHeader(Header.ACCEPT_LANGUAGE, false);
                headerMap.put(Header.ACCEPT_LANGUAGE, al);
            } else {
                al.removeValues();
            }

            al.setValue(locale.getLanguage(), false);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#addAcceptLanguage(java.util.Locale)
     */
    public void addAcceptLanguage(Locale locale) {
        Header al = headerMap.get(Header.ACCEPT_LANGUAGE);

        if (al == null) {
            al = new MultiLineHeader(Header.ACCEPT_LANGUAGE, false);
            headerMap.put(Header.ACCEPT_LANGUAGE, al);
        }

        al.setValue(locale.getLanguage(), false);

        // TODO - should be sorted with the other based on q value
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#setContentLanguage(java.util.Locale)
     */
    public void setContentLanguage(Locale locale) {
        setHeader(Header.CONTENT_LANGUAGE, locale.getLanguage());
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getContentLanguage()
     */
    public Locale getContentLanguage() {
        String cl = getHeader(Header.CONTENT_LANGUAGE);

        if (cl != null) {
            return new Locale(cl);
        } else {
        } // TODO locale identified by the charset parameter of the
          // Content-Type
          // header

        return Locale.getDefault();
    }

    public abstract void send() throws IOException;

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#isSecure()
     */
    public boolean isSecure() {
        return "TLS".equals(getTransport());
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#isCommitted()
     */
    public boolean isCommitted() {
        // TODO Auto-generated method stub
        return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getRemoteUser()
     */
    public String getRemoteUser() {
        return _user;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#isUserInRole(java.lang.String)
     */
    public boolean isUserInRole(String role) {
        if (_authModule == null) {
            return false;
        }

        String servletName = getApplicationSessionImpl().getCurrentServlet();

        return _authModule.hasRoleRefPermission(_method, servletName);
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#isUserInRole(java.lang.String)
     */
    public void setRoles(List<String> roles) {
        _roles = roles;
    }

    public List<String> getRoles() {
        return _roles;
    }

    public void setRole(String role) {
        _roles = new ArrayList<String>();
        _roles.add(role);
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#isUserInRole(java.lang.String)
     */
    public String popRole() {
        if (_roles != null) {
            int size = _roles.size();

            if (size > 0) {
                return _roles.remove(size - 1);
            }
        }

        return null;
    }

    public String peekRole() {
        int size = _roles.size();

        if (size > 0) {
            return _roles.get(size - 1);
        }

        return null;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getUserPrincipal()
     */
    public Principal getUserPrincipal() {
        return _principal;
    }

    public void setCertificate(X509Certificate[] cert) {
        _clientCert = cert;
    }

    public X509Certificate[] getCertificate() {
        return _clientCert;
    }

    public void setRemote(TargetTuple remote) {
        _remote = remote;
    }

    public TargetTuple getRemote() {
        return _remote;
    }

    public void setLocal(InetSocketAddress local) {
        _local = local;
    }

    public InetSocketAddress getLocal() {
        return _local;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getLocalAddr()
     */
    public String getLocalAddr() {
        if (_local != null) {
            return _local.getAddress().toString();
        }

        return null;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getLocalPort()
     */
    public int getLocalPort() {
        if (_local != null) {
            return _local.getPort();
        }

        return -1;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getRemoteAddr()
     */
    public String getRemoteAddr() {
        if (_remote != null) {
            return _remote.getIP();
        }

        return null;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getRemotePort()
     */
    public int getRemotePort() {
        if (_remote != null) {
            return _remote.getPort();
        }

        return -1;
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.servlet.sip.SipServletMessage#getTransport()
     */
    public String getTransport() {
        if (_remote != null) {
            return _remote.getProtocol().name().toUpperCase();
        }

        return null;
    }

    /****************************************************************************
     * Checks the To,From,Cseq,Call-Id
     *
     * @return
     */

    /**
     * Return true if no to tag has been set in the message otherwise false.
     *
     * @return true if no to tag has been set in the message otherwise false.
     */
    public boolean hasToTag() {
        return getTo().getParameter(AddressImpl.TAG_PARAM) != null;
    }

    public boolean isValidMessage() {
        // TODO implement
        return false;
    }

    public void pushTransactionDispatcher(Dispatcher disp) {
        _transactionStack.add(disp);
    }

    public void pushApplicationDispatcher(Dispatcher disp) {
        _applicationStack.add(disp);
    }

    public void clearTransactionDispatchers() {
        _transactionStack.clear();
    }

    public void clearApplicationDispatchers() {
        _applicationStack.clear();
    }

    public abstract Dispatcher popDispatcher();

    public abstract Dispatcher peekDispatcher();

    String toDebugString() {
        StringBuilder sb = new StringBuilder("Call-ID: ");
        sb.append(headerMap.get(Header.CALL_ID).getValue());
        sb.append(" To-tag: ");

        try {
            sb.append(headerMap.get(Header.TO).getAddressValue()
                               .getParameter(AddressImpl.TAG_PARAM));
        } catch (Exception e) {
            sb.append("undefined");
        }

        sb.append(" From-tag: ");

        try {
            sb.append(headerMap.get(Header.FROM).getAddressValue()
                               .getParameter(AddressImpl.TAG_PARAM));
        } catch (Exception e) {
            sb.append("undefined");
        }

        sb.append(" CSeq: ");
        sb.append(headerMap.get(Header.CSEQ).getValue());

        return sb.toString();
    }

    /**
     * The method called when a service called send method. This is checked in
     * the ApplicationDispatcher whether to continue executing other services or
     * stopping.
     *
     * @param hasSent
     *        A boolean.
     */
    public void setSent(boolean hasSent) {
        ThreadData threadData = new ThreadData();
        threadData.setSent(new Boolean(hasSent));

        // TODO cleanup so there is a method returning impl with boolean
        if (getSessionImpl() != null) {
            threadData.setSipApplicationSessionImpl(getSessionImpl()
                                                        .getApplicationSessionImpl());
        }

        _tlData.set(threadData);
    }

    /**
     * The method return a boolean indicating if a service has sent a response or
     * generate a request to break the execution of other services.
     *
     * @return
     */
    public boolean hasSent() {
        ThreadData threadData = _tlData.get();

        if (threadData == null) {
            return false;
        }

        Boolean sent = threadData.getSent();

        if (sent == null) {
            return false;
        }

        return threadData.getSent().booleanValue();
    }

    /**
     * A static method to return ThreadLocal<ThreadData> object.
     *
     * @return ThreadLocal<ThreadData>
     */
    public static ThreadData getThreadLocalData() {
        return _tlData.get();
    }

    /**
     * A static method to clean ThreadLocal<ThreadData> object.
     */
    public static void removeThreadLocalData() {
        _tlData.remove();
    }

    /**
     * States that a Contact header should be added to this message
     */
    public void indicateContact() {
        _IsContactIndicated = true;
    }

    /**
     * Returns that a Contact header should be added to this message
     *
     * @return whether a Contact header should be added to this message or not
     */
    public boolean isContactIndicated() {
        return _IsContactIndicated;
    }

    /**
     * This method returns the content-length for a multipart message.
     *
     * @param content -
     *        the content
     * @param contentType -
     *        the content type
     * @param charSet -
     *        encoding
     * @return content-length.
     * @throws UnsupportedEncodingException
     * @throws MessagingException
     */
    private int parseMultipart(Object content, String contentType,
        String charSet) throws UnsupportedEncodingException, MessagingException {
        // To parse the multipart, it must be a string
        String multipart = null;

        if (content instanceof byte[]) {
            // Build a string out of it
            multipart = new String((byte[]) content, charSet);
        } else if (content instanceof String) {
            multipart = (String) content;
        } else {
            throw new IllegalArgumentException(
                "Content object needs to be a Multipart object or a byte[] " +
                "or a String for MIME multipart type");
        }

        // Do the actual parsing
        _content_obj = new MimeMultipart(new StringDataSource(multipart,
                    contentType, charSet));

        return multipart.length();
    }

    /**
     * Used with getContent().
     *
     * @param charSet -
     *        the encoding to be used
     * @return - the content as a String encoded with charSet or null if the
     *         content is null.
     */
    private String getContentAsString(String charSet) {
        // If an object, return its toString()
        if (_content_obj != null) {
            return _content_obj.toString();
        } else if (_content_byte != null) {
            try {
                // Encode it
                return new String(_content_byte, charSet);
            } catch (UnsupportedEncodingException e) {
                throw new IllegalArgumentException("Unsupported encoding " + e);
            }
        }

        return null;
    }

    public boolean isHeadersComplete() {
        return _headersComplete;
    }

    public void setHeadersComplete(boolean complete) {
        _headersComplete = complete;
    }

    public boolean isMessageComplete() {
        return _messageComplete;
    }

    public void setMessageComplete(boolean complete) {
        _messageComplete = complete;

        String ctype = getContentType();

        if (ctype != null) {
            try {
                setContent(_content_byte, ctype);
            } catch (UnsupportedEncodingException e) {
            }
        }
    }

    public DialogFragment getDialog() {
        return _dialog;
    }

    public void setDialog(DialogFragment dialog) {
        _dialog = dialog;
    }

    public int getFragmentId() {
        return _fragmentId;
    }

    public void addInternalHeader(String name, String value) {
        Header header = null;
        String pretty = Header.format(name);

        if (headerMap.containsKey(pretty)) {
            header = headerMap.get(pretty);
            header.removeValues();
            header.setValue(value, true);
        } else {
            header = Header.createFormated(pretty, this);
            header.setValue(value, true);
            headerMap.put(pretty, header);
        }
    }

    /**
     * Returns true if the message has a body. This method was added for the
     * solution of HF73291, which says that a message with a body but missing a
     * Content-Type header should be rejected with a 400 response.
     *
     * @return
     */
    public boolean hasBody() {
        return (_content_byte != null) || (_content_obj != null);
    }

    /**
     * Extracts the Cseq number from the message.
     *
     * @param m
     *        the messge holding the Cseq value
     * @return the CSeq number
     */
    public int getCSeqNumber() throws NumberFormatException {
        String CSeq = getHeader("CSeq");

        if (CSeq == null) {
            throw new NumberFormatException(
                "Could not extract CSeq number from message.");
        }

        int posStart = 0;

        // trim
        while ((CSeq.charAt(posStart) == ' ') && (posStart < CSeq.length())) {
            ++posStart;
        }

        int posStop = posStart;

        // find end of integer, CSeq number
        while (Character.isDigit(CSeq.charAt(posStop)) &&
                (posStop < CSeq.length())) {
            ++posStop;
        }

        if (posStop >= CSeq.length()) {
            // the CSeq is corrupt
            throw new NumberFormatException(
                "Could not extract CSeq number from message.");
        }

        return Integer.parseInt(CSeq.substring(posStart, posStop));
    }

    public void setUser(String user) {
        _user = user;
    }

    public void setUserPrincipal(Principal value) {
        _principal = value;
    }

    /**
     * Translate the <code>SipServletMessageImpl</code> bytes into
     * a <code>ByteBuffer</code>.
     *
     * @param byteBuffer The byteBuffer to store the bytes.
     * @return byteBuffer The byteBuffer to store the bytes.
     */
    abstract public ByteBuffer toBuffer(ByteBuffer byteBuffer)
        throws UnsupportedEncodingException;

    public void setAuthModule(AuthModule module) {
        this._authModule = module;
    }

    //Optimazation in order not have to do instanceof
    public enum SipMessageType {SipRequest,
        SipResponse;
    }
}
