/*
 * Copyright 2001-2004 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.axis ;

import org.apache.axis.attachments.Attachments;
import org.apache.axis.client.AxisClient;
import org.apache.axis.components.logger.LogFactory;
import org.apache.axis.description.OperationDesc;
import org.apache.axis.description.ServiceDesc;
import org.apache.axis.encoding.TypeMapping;
import org.apache.axis.encoding.TypeMappingRegistry;
import org.apache.axis.constants.Style;
import org.apache.axis.constants.Use;
import org.apache.axis.handlers.soap.SOAPService;
import org.apache.axis.schema.SchemaVersion;
import org.apache.axis.session.Session;
import org.apache.axis.soap.SOAPConstants;
import org.apache.axis.utils.JavaUtils;
import org.apache.axis.utils.LockableHashtable;
import org.apache.axis.utils.Messages;
import org.apache.commons.logging.Log;

import javax.xml.namespace.QName;
import javax.xml.rpc.Call;
import javax.xml.rpc.handler.soap.SOAPMessageContext;
import java.io.File;
import java.util.ArrayList;
import java.util.Hashtable;

// fixme: fields are declared throughout this class, some at the top, and some
//  near to where they are used. We should move all field declarations into a
//  single block - it makes it easier to see what is decalred in this class and
//  what is inherited. It also makes it easier to find them.
/**
 * A MessageContext is the Axis implementation of the javax
 * SOAPMessageContext class, and is core to message processing
 * in handlers and other parts of the system.
 *
 * This class also contains constants for accessing some
 * well-known properties. Using a hierarchical namespace is
 * strongly suggested in order to lower the chance for
 * conflicts.
 *
 * (These constants should be viewed as an explicit list of well
 *  known and widely used context keys, there's nothing wrong
 *  with directly using the key strings. This is the reason for
 *  the hierarchical constant namespace.
 *
 *  Actually I think we might just list the keys in the docs and
 *  provide no such constants since they create yet another
 *  namespace, but we'd have no compile-time checks then.
 *
 *  Whaddya think? - todo by Jacek)
 *
 *
 * @author Doug Davis (dug@us.ibm.com)
 * @author Jacek Kopecky (jacek@idoox.com)
 */
public class MessageContext implements SOAPMessageContext {
    /** The <code>Log</code> used for logging all messages. */
    protected static Log log =
            LogFactory.getLog(MessageContext.class.getName());

    /**
     * The request message.  If we're on the client, this is the outgoing
     * message heading to the server.  If we're on the server, this is the
     * incoming message we've received from the client.
     */
    private Message      requestMessage;

    /**
     * The response message.  If we're on the server, this is the outgoing
     * message heading back to the client.  If we're on the client, this is the
     * incoming message we've received from the server.
     */
    private Message      responseMessage;

    /**
     * That unique key/name that the next router/dispatch handler should use
     * to determine what to do next.
     */
    private String       targetService;

    /**
     * The name of the Transport which this message was received on (or is
     * headed to, for the client).
     */
    private String       transportName;

    /**
     * The default <code>ClassLoader</code> that this service should use.
     */
    private ClassLoader  classLoader;

    /**
     * The AxisEngine which this context is involved with.
     */
    private AxisEngine   axisEngine;

    /**
     * A Session associated with this request.
     */
    private Session      session;

    /**
     * Should we track session state, or not?
     * default is not.
     * Could potentially refactor this so that
     * maintainSession iff session != null...
     */
    private boolean      maintainSession = false;

    // fixme: ambiguity here due to lac of docs - havePassedPivot vs
    //  request/response, vs sending/processing & recieving/responding
    //  I may have just missed the key bit of text
    /**
     * Are we doing request stuff, or response stuff? True if processing
     * response (I think).
     */
    private boolean      havePassedPivot = false;

    /**
     * Maximum amount of time to wait on a request, in milliseconds.
     */
    private int          timeout = Constants.DEFAULT_MESSAGE_TIMEOUT;

    /**
     * An indication of whether we require "high fidelity" recording of
     * deserialized messages for this interaction.  Defaults to true for
     * now, and can be set to false, usually at service-dispatch time.
     */
    private boolean      highFidelity = true;

    /**
     * Storage for an arbitrary bag of properties associated with this
     * MessageContext.
     */
    private LockableHashtable bag = new LockableHashtable();

    /*
     * These variables are logically part of the bag, but are separated
     * because they are used often and the Hashtable is more expensive.
     *
     * fixme: this may be fixed by moving to a plain Map impl like HashMap.
     *  Alternatively, we could hide all this magic behind a custom Map impl -
     *  is synchronization on the map needed? these properties aren't
     *  synchronized so I'm guessing not.
     */
    private String  username       = null;
    private String  password       = null;
    private String  encodingStyle  = Use.ENCODED.getEncoding();
    private boolean useSOAPAction  = false;
    private String  SOAPActionURI  = null;

    /**
     * SOAP Actor roles.
     */
    private String[] roles;

    /** Our SOAP namespaces and such. */
    private SOAPConstants soapConstants = Constants.DEFAULT_SOAP_VERSION;

    /** Schema version information - defaults to 2001. */
    private SchemaVersion schemaVersion = SchemaVersion.SCHEMA_2001;

    /** Our current operation. */
    private OperationDesc currentOperation = null;

    /**
     * The current operation.
     *
     * @return the current operation; may be <code>null</code>
     */
    public  OperationDesc getOperation()
    {
        return currentOperation;
    }

    /**
     * Set the current operation.
     *
     * @param operation  the <code>Operation</code> this context is executing
     */
    public void setOperation(OperationDesc operation)
    {
        currentOperation = operation;
    }

    /**
     * Returns a list of operation descriptors that could may
     * possibly match a body containing an element of the given QName.
     * For non-DOCUMENT, the list of operation descriptors that match
     * the name is returned.  For DOCUMENT, all the operations that have
     * qname as a parameter are returned
     *
     * @param qname of the first element in the body
     * @return list of operation descriptions
     * @throws AxisFault if the operation names could not be looked up
     */
    public OperationDesc [] getPossibleOperationsByQName(QName qname) throws AxisFault
    {
        if (currentOperation != null) {
            return new OperationDesc [] { currentOperation };
        }

        OperationDesc [] possibleOperations = null;

        if (serviceHandler == null) {
            try {
                if (log.isDebugEnabled()) {
                    log.debug(Messages.getMessage("dispatching00",
                                                   qname.getNamespaceURI()));
                }

                // Try looking this QName up in our mapping table...
                setService(axisEngine.getConfig().
                           getServiceByNamespaceURI(qname.getNamespaceURI()));
            } catch (ConfigurationException e) {
                // Didn't find one...
            }

        }

        if (serviceHandler != null) {
            ServiceDesc desc = serviceHandler.getInitializedServiceDesc(this);

            if (desc != null) {
                if (desc.getStyle() != Style.DOCUMENT) {
                    possibleOperations = desc.getOperationsByQName(qname);
                } else {
                    // DOCUMENT Style
                    // Get all of the operations that have qname as
                    // a possible parameter QName
                    ArrayList allOperations = desc.getOperations();
                    ArrayList foundOperations = new ArrayList();
                    for (int i=0; i < allOperations.size(); i++ ) {
                        OperationDesc tryOp =
                            (OperationDesc) allOperations.get(i);
                        if (tryOp.getParamByQName(qname) != null) {
                            foundOperations.add(tryOp);
                        }
                    }
                    if (foundOperations.size() > 0) {
                        possibleOperations = (OperationDesc[])
                            JavaUtils.convert(foundOperations,
                                              OperationDesc[].class);
                    }
                }
            }
        }
        return possibleOperations;
    }

    /**
     * get the first possible operation that could match a
     * body containing an element of the given QName. Sets the currentOperation
     * field in the process; if that field is already set then its value
     * is returned instead
     * @param qname name of the message body
     * @return an operation or null
     * @throws AxisFault
     */
    public OperationDesc getOperationByQName(QName qname) throws AxisFault
    {
        if (currentOperation == null) {
            OperationDesc [] possibleOperations = getPossibleOperationsByQName(qname);
            if (possibleOperations != null && possibleOperations.length > 0) {
                currentOperation = possibleOperations[0];
            }
        }

        return currentOperation;
    }

    /**
     * Get the active message context.
     *
     * @return the current active message context
     */
    public static MessageContext getCurrentContext() {
       return AxisEngine.getCurrentMessageContext();
    }

    /**
     * Temporary directory to store attachments.
     */
    protected static String systemTempDir= null;
    /**
     * set the temp dir
     * TODO: move this piece of code out of this class and into a utilities
     * class.
     */
    static {
        try {
            //get the temp dir from the engine
            systemTempDir=AxisProperties.getProperty(AxisEngine.ENV_ATTACHMENT_DIR);
        } catch(Throwable t) {
            systemTempDir= null;
        }

        if(systemTempDir== null) {
            try {
                //or create and delete a file in the temp dir to make
                //sure we have write access to it.
                File tf= File.createTempFile("Axis", ".tmp");
                File dir= tf.getParentFile();
                if (tf.exists()) {
                    tf.delete();
                }
                if (dir != null) {
                  systemTempDir= dir.getCanonicalPath();
                }
            } catch(Throwable t) {
                log.debug("Unable to find a temp dir with write access");
                systemTempDir= null;
            }
        }
    }

    /**
     * Create a message context.
     * @param engine the controlling axis engine. Null is actually accepted here,
     * though passing a null engine in is strongly discouraged as many of the methods
     * assume that it is in fact defined.
     */
    public MessageContext(AxisEngine engine) {
        this.axisEngine = engine;

        if(null != engine){
            java.util.Hashtable opts= engine.getOptions();
            String attachmentsdir= null;
            if(null!=opts) {
                attachmentsdir= (String) opts.get(AxisEngine.PROP_ATTACHMENT_DIR);
            }
            if(null == attachmentsdir) {
                attachmentsdir= systemTempDir;
            }
            if(attachmentsdir != null){
                setProperty(ATTACHMENTS_DIR, attachmentsdir);
            }

            // If SOAP 1.2 has been specified as the default for the engine,
            // switch the constants over.
            String defaultSOAPVersion = (String)engine.getOption(
                                                 AxisEngine.PROP_SOAP_VERSION);
            if (defaultSOAPVersion != null && "1.2".equals(defaultSOAPVersion)) {
                setSOAPConstants(SOAPConstants.SOAP12_CONSTANTS);
            }

            String singleSOAPVersion = (String)engine.getOption(
                                        AxisEngine.PROP_SOAP_ALLOWED_VERSION);
            if (singleSOAPVersion != null) {
                if ("1.2".equals(singleSOAPVersion)) {
                    setProperty(Constants.MC_SINGLE_SOAP_VERSION,
                                SOAPConstants.SOAP12_CONSTANTS);
                } else if ("1.1".equals(singleSOAPVersion)) {
                    setProperty(Constants.MC_SINGLE_SOAP_VERSION,
                                SOAPConstants.SOAP11_CONSTANTS);
                }
            }
        }
    }

    /**
     * during finalization, the dispose() method is called.
     * @see #dispose()
     */
    protected void finalize() {
        dispose();
    }

    /**
     * Mappings of QNames to serializers/deserializers (and therfore
     * to Java types).
     */
    private TypeMappingRegistry mappingRegistry = null;

    /**
     * Replace the engine's type mapping registry with a local one. This will
     * have no effect on any type mappings obtained before this call.
     *
     * @param reg  the new <code>TypeMappingRegistry</code>
     */
    public void setTypeMappingRegistry(TypeMappingRegistry reg) {
        mappingRegistry = reg;
    }

    /**
     * Get the currently in-scope type mapping registry.
     *
     * By default, will return a reference to the AxisEngine's TMR until
     * someone sets our local one (usually as a result of setting the
     * serviceHandler).
     *
     * @return the type mapping registry to use for this request.
     */
    public TypeMappingRegistry getTypeMappingRegistry() {
        if (mappingRegistry == null) {
            return axisEngine.getTypeMappingRegistry();
        }

        return mappingRegistry;
    }

    /**
     * Return the type mapping currently in scope for our encoding style.
     *
     * @return the type mapping
     */
    public TypeMapping getTypeMapping()
    {
        return (TypeMapping)getTypeMappingRegistry().
                getTypeMapping(encodingStyle);
    }

    /**
     * The name of the transport for this context.
     *
     * @return the transport name
     */
    public String getTransportName()
    {
        return transportName;
    }

    // fixme: the transport names should be a type-safe e-num, or the range
    //  of legal values should be specified in the documentation and validated
    //  in the method (raising IllegalArgumentException)
    /**
     * Set the transport name for this context.
     *
     * @param transportName the name of the transport
     */
    public void setTransportName(String transportName)
    {
        this.transportName = transportName;
    }

    /**
     * Get the <code>SOAPConstants</code> used by this message context.
     *
     * @return the soap constants
     */
    public SOAPConstants getSOAPConstants() {
        return soapConstants;
    }

    /**
     * Set the <code>SOAPConstants</code> used by this message context.
     * This may also affect the encoding style.
     *
     * @param soapConstants  the new soap constants to use
     */
    public void setSOAPConstants(SOAPConstants soapConstants) {
        // when changing SOAP versions, remember to keep the encodingURI
        // in synch.
        if (this.soapConstants.getEncodingURI().equals(encodingStyle)) {
            encodingStyle = soapConstants.getEncodingURI();
        }

        this.soapConstants = soapConstants;
    }

    /**
     * Get the XML schema version information.
     *
     * @return the <code>SchemaVersion</code> in use
     */
    public SchemaVersion getSchemaVersion() {
        return schemaVersion;
    }

    /**
     * Set the XML schema version this message context will use.
     *
     * @param schemaVersion  the new <code>SchemaVersion</code>
     */
    public void setSchemaVersion(SchemaVersion schemaVersion) {
        this.schemaVersion = schemaVersion;
    }

    /**
     * Get the current session.
     *
     * @return the <code>Session</code> this message context is within
     */
    public Session getSession()
    {
        return session;
    }

    /**
     * Set the current session.
     *
     * @param session  the new <code>Session</code>
     */
    public void setSession(Session session)
    {
        this.session = session;
    }

    /**
     * Indicates if the opration is encoded.
     *
     * @return <code>true</code> if it is encoded, <code>false</code> otherwise
     */
    public boolean isEncoded() {
        return (getOperationUse() == Use.ENCODED);
        //return soapConstants.getEncodingURI().equals(encodingStyle);
    }

    /**
     * Set whether we are maintaining session state.
     *
     * @param yesno flag to set to <code>true</code> to maintain sessions
     */
    public void setMaintainSession (boolean yesno) {
        maintainSession = yesno;
    }

    /**
     * Discover if we are maintaining session state.
     *
     * @return <code>true</code> if we are maintaining state, <code>false</code>
     *              otherwise
     */
    public boolean getMaintainSession () {
        return maintainSession;
    }

    /**
     * Get the request message.
     *
     * @return the request message (may be null).
     */
    public Message getRequestMessage() {
        return requestMessage ;
    }

    /**
     * Set the request message, and make sure that message is associated
     * with this MessageContext.
     *
     * @param reqMsg the new request Message.
     */
    public void setRequestMessage(Message reqMsg) {
        requestMessage = reqMsg ;
        if (requestMessage != null) {
            requestMessage.setMessageContext(this);
        }
    }

    /**
     * Get the response message.
     *
     * @return the response message (may be null).
     */
    public Message getResponseMessage() { return responseMessage ; }

    /**
     * Set the response message, and make sure that message is associated
     * with this MessageContext.
     *
     * @param respMsg the new response Message.
     */
    public void setResponseMessage(Message respMsg) {
        responseMessage = respMsg;
        if (responseMessage != null) {
            responseMessage.setMessageContext(this);

            //if we have received attachments of a particular type
            // than that should be the default type to send.
            Message reqMsg = getRequestMessage();
            if (null != reqMsg) {
                Attachments reqAttch = reqMsg.getAttachmentsImpl();
                Attachments respAttch = respMsg.getAttachmentsImpl();
                if (null != reqAttch && null != respAttch) {
                    if (respAttch.getSendType() == Attachments.SEND_TYPE_NOTSET)
                        //only if not explicity set.
                        respAttch.setSendType(reqAttch.getSendType());
                }
            }
        }
    }

    /**
     * Return the current (i.e. request before the pivot, response after)
     * message.
     *
     * @return the current <code>Message</code>
     */
    public Message getCurrentMessage()
    {
        return (havePassedPivot ? responseMessage : requestMessage);
    }

    /**
     *  Gets the SOAPMessage from this message context.
     *
     *  @return the <code>SOAPMessage</code>, <code>null</code> if no request
     *          <code>SOAPMessage</code> is present in this
     *          <code>SOAPMessageContext</code>
     */
    public javax.xml.soap.SOAPMessage getMessage() {
        return getCurrentMessage();
    }

    /**
     * Set the current message. This will set the request before the pivot,
     * and the response afterwards, as guaged by the passedPivod property.
     *
     * @param curMsg  the <code>Message</code> to assign
     */
    public void setCurrentMessage(Message curMsg)
    {
        if ( curMsg != null )
            curMsg.setMessageContext(this);

        if (havePassedPivot) {
            responseMessage = curMsg;
        } else {
            requestMessage = curMsg;
        }
    }

    /**
     * Sets the SOAPMessage for this message context.
     * This is equivalent to casting <code>message</code> to
     * <code>Message</code> and then passing it on to
     * <code>setCurrentMessage()</code>.
     *
     * @param message  the <code>SOAPMessage</code> this context is for
     */
    public void setMessage(javax.xml.soap.SOAPMessage message) {
        setCurrentMessage((Message)message);
    }

    /**
     * Determine when we've passed the pivot.
     *
     * @return <code>true</code> if we have, <code>false</code> otherwise
     */
    public boolean getPastPivot()
    {
        return havePassedPivot;
    }

    // fixme: is there any legitimate case where we could pass the pivot and
    //  then go back again? Is there documentation about the life-cycle of a
    //  MessageContext, and in particular the re-use of instances that would be
    //  relevant?
    /**
     * Indicate when we've passed the pivot.
     *
     * @param pastPivot  true if we are past the pivot point, false otherwise
     */
    public void setPastPivot(boolean pastPivot)
    {
        havePassedPivot = pastPivot;
    }

    /**
     * Set timeout in our MessageContext.
     *
     * @param value the maximum amount of time, in milliseconds
     */
    public void setTimeout (int value) {
        timeout = value;
    }

    /**
     * Get timeout from our MessageContext.
     *
     * @return value the maximum amount of time, in milliseconds
     */
    public int getTimeout () {
        return timeout;
    }

    /**
     * Get the classloader, implicitly binding to the thread context
     * classloader if an override has not been supplied.
     *
     * @return the class loader
     */
    public ClassLoader getClassLoader() {
        if ( classLoader == null ) {
            classLoader = Thread.currentThread().getContextClassLoader();
        }
        return( classLoader );
    }

    /**
     * Set a new classloader. Setting to null will result in getClassLoader()
     * binding back to the thread context class loader.
     *
     * @param cl    the new <code>ClassLoader</code> or <code>null</code>
     */
    public void setClassLoader(ClassLoader cl ) {
        classLoader = cl ;
    }

    /**
     * Get the name of the targed service for this message.
     *
     * @return the target service
     */
    public String getTargetService() {
        return targetService;
    }

    /**
     * Get the axis engine. This will be <code>null</code> if the message was
     * created outside an engine
     *
     * @return the current axis engine
     */
    public AxisEngine getAxisEngine()
    {
        return axisEngine;
    }

    /**
     * Set the target service for this message.
     * <p>
     * This looks up the named service in the registry, and has
     * the side effect of setting our TypeMappingRegistry to the
     * service's.
     *
     * @param tServ the name of the target service
     * @throws AxisFault  if anything goes wrong in resolving or setting the
     *              service
     */
    public void setTargetService(String tServ) throws AxisFault {
        log.debug("MessageContext: setTargetService(" + tServ+")");

        if (tServ == null) {
            setService(null);
        }
        else {
            try {
                setService(getAxisEngine().getService(tServ));
            } catch (AxisFault fault) {
                // If we're on the client, don't throw this fault...
                if (!isClient()) {
                    throw fault;
                }
            }
        }
        targetService = tServ;
    }

    /** ServiceHandler is the handler that is the "service".  This handler
     * can (and probably will actually be a chain that contains the
     * service specific request/response/pivot point handlers
     */
    private SOAPService serviceHandler ;

    /**
     * Get the <code>SOAPService</code> used to handle services in this
     * context.
     *
     * @return the service handler
     */
    public SOAPService getService() {
        return  serviceHandler;
    }

    /**
     * Set the <code>SOAPService</code> used to handle services in this
     * context. This method configures a wide range of
     * <code>MessageContext</code> properties to suit the handler.
     *
     * @param sh the new service handler
     * @throws AxisFault if the service could not be set
     */
    public void setService(SOAPService sh) throws AxisFault
    {
        log.debug("MessageContext: setServiceHandler("+sh+")");
        serviceHandler = sh;
        if (sh != null) {
            if(!sh.isRunning()) {
                throw new AxisFault(Messages.getMessage("disabled00"));
            }
            targetService = sh.getName();
            SOAPService service = sh;
            TypeMappingRegistry tmr = service.getTypeMappingRegistry();
            setTypeMappingRegistry(tmr);

            // styles are not "soap version aware" so compensate...
            setEncodingStyle(service.getUse().getEncoding());

            // This MessageContext should now defer properties it can't find
            // to the Service's options.
            bag.setParent(sh.getOptions());

            // Note that we need (or don't need) high-fidelity SAX recording
            // of deserialized messages according to the setting on the
            // new service.
            highFidelity = service.needsHighFidelityRecording();

            service.getInitializedServiceDesc(this);
        }
    }

    /**
     * Let us know whether this is the client or the server.
     *
     * @return true if we are a client
     */
    public boolean isClient()
    {
        return (axisEngine instanceof AxisClient);
    }

    // fixme: public final statics tend to go in a block at the top of the
    //  class deffinition, not marooned in the middle
    // fixme: chose public static final /or/ public final static
    /** Contains an instance of Handler, which is the
     *  ServiceContext and the entrypoint of this service.
     *
     *  (if it has been so configured - will our deployment
     *   tool do this by default?  - todo by Jacek)
     */
    public static final String ENGINE_HANDLER      = "engine.handler";

    /** This String is the URL that the message came to.
     */
    public static final String TRANS_URL           = "transport.url";

    /** Has a quit been requested? Hackish... but useful... -- RobJ */
    public static final String QUIT_REQUESTED = "quit.requested";

    /** Place to store an AuthenticatedUser. */
    public static final String AUTHUSER            = "authenticatedUser";

    /** If on the client - this is the Call object. */
    public static final String CALL                = "call_object" ;

    /** Are we doing Msg vs RPC? - For Java Binding. */
    public static final String IS_MSG              = "isMsg" ;

    /** The directory where in coming attachments are created. */
    public static final String ATTACHMENTS_DIR   = "attachments.directory" ;

    /** A boolean param, to control whether we accept missing parameters
     * as nulls or refuse to acknowledge them.
     */
    public final static String ACCEPTMISSINGPARAMS = "acceptMissingParams";

    /** The value of the property is used by service WSDL generation (aka ?WSDL)
     * For the service's interface namespace if not set TRANS_URL property is used.
     */
    public static final String WSDLGEN_INTFNAMESPACE      = "axis.wsdlgen.intfnamespace";

    /** The value of the property is used by service WSDL generation (aka ?WSDL).
     * For the service's location if not set TRANS_URL property is used.
     *  (helps provide support through proxies.
     */
    public static final String WSDLGEN_SERV_LOC_URL      = "axis.wsdlgen.serv.loc.url";

    // fixme: should this be a type-safe e-num?
    /** The value of the property is used by service WSDL generation (aka ?WSDL).
     *  Set this property to request a certain level of HTTP.
     *  The values MUST use org.apache.axis.transport.http.HTTPConstants.HEADER_PROTOCOL_10
     *    for HTTP 1.0
     *  The values MUST use org.apache.axis.transport.http.HTTPConstants.HEADER_PROTOCOL_11
     *    for HTTP 1.1
     */
    public static final String HTTP_TRANSPORT_VERSION  = "axis.transport.version";

    // fixme: is this the name of a security provider, or the name of a security
    //  provider class, or the actualy class of a security provider, or
    //  something else?
    /**
     * The security provider.
     */
    public static final String SECURITY_PROVIDER = "securityProvider";

    /*
     * IMPORTANT.
     * If adding any new constants to this class. Make them final. The
     * ones above are left non-final for compatibility reasons.
     */

    /**
     * Get a <code>String</code> property by name.
     *
     * @param propName the name of the property to fetch
     * @return the value of the named property
     * @throws ClassCastException if the property named does not have a
     *              <code>String</code> value
     */
    public String getStrProp(String propName) {
        return (String) getProperty(propName);
    }

    /**
     * Tests to see if the named property is set in the 'bag', returning
     * <code>false</code> if it is not present at all.
     * This is equivalent to <code>isPropertyTrue(propName, false)</code>.
     *
     * @param propName  the name of the property to check
     * @return true or false, depending on the value of the property
     */
    public boolean isPropertyTrue(String propName) {
        return isPropertyTrue(propName, false);
    }

    /**
     * Test if a property is set to something we consider to be true in the
     * 'bag'.
     * <ul>
     * <li>If not there then <code>defaultVal</code> is returned.</li>
     * <li>If there, then...<ul>
     *   <li>if its a <code>Boolean</code>, we'll return booleanValue()</li>
     *   <li>if its an <code>Integer</code>,  we'll return <code>false</code>
     *   if its <code>0</code> else <code>true</code></li>
     *   <li>if its a <code>String</code> we'll return <code>false</code> if its
     *   <code>"false"</code>" or <code>"0"</code> else <code>true</code></li>
     *   <li>All other types return <code>true</code></li>
     * </ul></li>
     * </ul>
     *
     * @param propName  the name of the property to check
     * @param defaultVal  the default value
     * @return true or false, depending on the value of the property
     */
    public boolean isPropertyTrue(String propName, boolean defaultVal) {
        return JavaUtils.isTrue(getProperty(propName), defaultVal);
    }

    /**
     * Allows you to set a named property to the passed in value.
     * There are a few known properties (like username, password, etc)
     * that are variables in Call.  The rest of the properties are
     * stored in a Hashtable.  These common properties should be
     * accessed via the accessors for speed/type safety, but they may
     * still be obtained via this method.  It's up to one of the
     * Handlers (or the Axis engine itself) to go looking for
     * one of them.
     *
     * @param name  Name of the property
     * @param value Value of the property
     */
    public void setProperty(String name, Object value) {
        if (name == null || value == null) {
            return;
            // Is this right?  Shouldn't we throw an exception like:
            // throw new IllegalArgumentException(msg);
        }
        else if (name.equals(Call.USERNAME_PROPERTY)) {
            if (!(value instanceof String)) {
                throw new IllegalArgumentException(
                        Messages.getMessage("badProp00", new String[] {
                        name, "java.lang.String", value.getClass().getName()}));
            }
            setUsername((String) value);
        }
        else if (name.equals(Call.PASSWORD_PROPERTY)) {
            if (!(value instanceof String)) {
                throw new IllegalArgumentException(
                        Messages.getMessage("badProp00", new String[] {
                        name, "java.lang.String", value.getClass().getName()}));
            }
            setPassword((String) value);
        }
        else if (name.equals(Call.SESSION_MAINTAIN_PROPERTY)) {
            if (!(value instanceof Boolean)) {
                throw new IllegalArgumentException(
                        Messages.getMessage("badProp00", new String[]
                        {name,
                        "java.lang.Boolean",
                        value.getClass().getName()}));
            }
            setMaintainSession(((Boolean) value).booleanValue());
        }
        else if (name.equals(Call.SOAPACTION_USE_PROPERTY)) {
            if (!(value instanceof Boolean)) {
                throw new IllegalArgumentException(
                        Messages.getMessage("badProp00", new String[]
                        {name,
                        "java.lang.Boolean",
                        value.getClass().getName()}));
            }
            setUseSOAPAction(((Boolean) value).booleanValue());
        }
        else if (name.equals(Call.SOAPACTION_URI_PROPERTY)) {
            if (!(value instanceof String)) {
                throw new IllegalArgumentException(
                        Messages.getMessage("badProp00", new String[]
                        {name,
                        "java.lang.String",
                        value.getClass().getName()}));
            }
            setSOAPActionURI((String) value);
        }
        else if (name.equals(Call.ENCODINGSTYLE_URI_PROPERTY)) {
            if (!(value instanceof String)) {
                throw new IllegalArgumentException(
                        Messages.getMessage("badProp00", new String[]
                        {name,
                        "java.lang.String",
                        value.getClass().getName()}));
            }
            setEncodingStyle((String) value);
        }
        else {
            bag.put(name, value);
        }
    } // setProperty

    /**
     *  Returns true if the MessageContext contains a property with the specified name.
     *  @param   name Name of the property whose presense is to be tested
     *  @return  Returns true if the MessageContext contains the
          property; otherwise false
     */
    public boolean containsProperty(String name) {
        Object propertyValue = getProperty(name);
        return (propertyValue != null);
    }

    /**
     * Returns an <code>Iterator</code> view of the names of the properties in
     * this <code>MessageContext</code>.
     *
     * @return an <code>Iterator</code> over all property names
     */
    public java.util.Iterator getPropertyNames() {
        // fixme: this is potentially unsafe for the caller - changing the
        //  properties will kill the iterator. Consider iterating over a copy:
        // return new HashSet(bag.keySet()).iterator();
        return bag.keySet().iterator();
    }

    /**
     *  Returns an Iterator view of the names of the properties 
     *  in this MessageContext and any parents of the LockableHashtable
     *  @return Iterator for the property names
     */
    public java.util.Iterator getAllPropertyNames() {
        return bag.getAllKeys().iterator();
    }

    /**
     * Returns the value associated with the named property - or null if not
     * defined/set.
     *
     * @param name  the property name
     * @return Object value of the property - or null
     */
    public Object getProperty(String name) {
        if (name != null) {
            if (name.equals(Call.USERNAME_PROPERTY)) {
                return getUsername();
            }
            else if (name.equals(Call.PASSWORD_PROPERTY)) {
                return getPassword();
            }
            else if (name.equals(Call.SESSION_MAINTAIN_PROPERTY)) {
                return getMaintainSession() ? Boolean.TRUE : Boolean.FALSE;
            }
            else if (name.equals(Call.OPERATION_STYLE_PROPERTY)) {
                return (getOperationStyle() == null) ? null : getOperationStyle().getName();
            }
            else if (name.equals(Call.SOAPACTION_USE_PROPERTY)) {
                return useSOAPAction() ? Boolean.TRUE : Boolean.FALSE;
            }
            else if (name.equals(Call.SOAPACTION_URI_PROPERTY)) {
                return getSOAPActionURI();
            }
            else if (name.equals(Call.ENCODINGSTYLE_URI_PROPERTY)) {
                return getEncodingStyle();
            }
            else if (bag == null) {
                return null;
            }
            else {
                return bag.get(name);
            }
        }
        else {
            return null;
        }
    }

    // fixme: this makes no copy of parent, so later modifications to parent
    //  can alter this context - is this intended? If so, it needs documenting.
    //  If not, it needs fixing.
    /**
     * Set the Hashtable that contains the default values for our
     * properties.
     *
     * @param parent
     */
    public void setPropertyParent(Hashtable parent)
    {
        bag.setParent(parent);
    }

    /**
     * Set the username.
     *
     * @param username  the new user name
     */
    public void setUsername(String username) {
        this.username = username;
    } // setUsername

    /**
     * Get the user name.
     *
     * @return the user name as a <code>String</code>
     */
    public String getUsername() {
        return username;
    } // getUsername

    /**
     * Set the password.
     *
     * @param password  a <code>String</code> containing the new password
     */
    public void setPassword(String password) {
        this.password = password;
    } // setPassword

    /**
     * Get the password.
     *
     * @return the current password <code>String</code>
     */
    public String getPassword() {
        return password;
    } // getPassword

    /**
     * Get the operation style. This is either the style of the current
     * operation or if that is not set, the style of the service handler, or
     * if that is not set, <code>Style.RPC</code>.
     *
     * @return the <code>Style</code> of this message
     */
    public Style getOperationStyle() {
        if (currentOperation != null) {
            return currentOperation.getStyle();
        }

        if (serviceHandler != null) {
            return serviceHandler.getStyle();
        }

        return Style.RPC;
    } // getOperationStyle

    /**
     * Get the operation use.
     *
     * @return the operation <code>Use</code>
     */
    public Use getOperationUse() {
        if (currentOperation != null) {
            return currentOperation.getUse();
        }

        if (serviceHandler != null) {
            return serviceHandler.getUse();
        }

        return Use.ENCODED;
    } // getOperationUse

    /**
     * Enable or dissable the use of soap action information. When enabled,
     * the message context will attempt to use the soap action URI
     * information during binding of soap messages to service methods. When
     * dissabled, it will make no such attempt.
     *
     * @param useSOAPAction  <code>true</code> if soap action URI information
     *              should be used, <code>false</code> otherwise
     */
    public void setUseSOAPAction(boolean useSOAPAction) {
        this.useSOAPAction = useSOAPAction;
    } // setUseSOAPAction

    // fixme: this doesn't follow beany naming conventions - should be
    //  isUseSOAPActions or getUseSOAPActions or something prettier
    /**
     * Indicates wether the soap action URI is being used or not.
     *
     * @return <code>true</code> if it is, <code>false</code> otherwise
     */
    public boolean useSOAPAction() {
        return useSOAPAction;
    } // useSOAPAction

    // fixme: this throws IllegalArgumentException but never raises it -
    //  perhaps in a sub-class?
    // fixme: IllegalArgumentException is unchecked. Best practice says you
    //  should document unchecked exceptions, but not list them in throws
    /**
     * Set the soapAction URI.
     *
     * @param SOAPActionURI  a <code>String</code> giving the new soap action
     *              URI
     * @throws IllegalArgumentException if the URI is not liked
     */
    public void setSOAPActionURI(String SOAPActionURI)
            throws IllegalArgumentException {
        this.SOAPActionURI = SOAPActionURI;
    } // setSOAPActionURI

    /**
     * Get the soapAction URI.
     *
     * @return the URI of this soap action
     */
    public String getSOAPActionURI() {
        return SOAPActionURI;
    } // getSOAPActionURI

    /**
     * Sets the encoding style to the URI passed in.
     *
     * @param namespaceURI URI of the encoding to use.
     */
    public void setEncodingStyle(String namespaceURI) {
        if (namespaceURI == null) {
            namespaceURI = Constants.URI_LITERAL_ENC;
        }
        else if (Constants.isSOAP_ENC(namespaceURI)) {
            namespaceURI = soapConstants.getEncodingURI();
        }

        encodingStyle = namespaceURI;
    } // setEncodingStype

    /**
     * Returns the encoding style as a URI that should be used for the SOAP
     * message.
     *
     * @return String URI of the encoding style to use
     */
    public String getEncodingStyle() {
        return encodingStyle;
    } // getEncodingStyle

    public void removeProperty(String propName)
    {
        if (bag != null) {
            bag.remove(propName);
        }
    }

    /**
     * Return this context to a clean state.
     */
    public void reset()
    {
        if (bag != null) {
            bag.clear();
        }
        serviceHandler = null;
        havePassedPivot = false;
        currentOperation = null;
    }

    /**
     * Read the high fidelity property.
     * <p>
     * Some behavior may be apropreate for high fidelity contexts that is not
     * relevant for low fidelity ones or vica-versa.
     *
     * @return <code>true</code> if the context is high fidelity,
     *              <code>false</code> otherwise
     */
    public boolean isHighFidelity() {
        return highFidelity;
    }

    /**
     * Set the high fidelity propert.
     * <p>
     * Users of the context may be changing what they do based upon this flag.
     *
     * @param highFidelity  the new value of the highFidelity property
     */
    public void setHighFidelity(boolean highFidelity) {
        this.highFidelity = highFidelity;
    }

    /**
     * Gets the SOAP actor roles associated with an execution of the
     * <code>HandlerChain</code> and its contained <code>Handler</code>
     * instances.
     * <p>
     * <i>Not (yet) implemented method in the SOAPMessageContext interface</i>.
     * <p>
     * <b>Note:</b> SOAP actor roles apply to the SOAP node and are managed
     * using <code>HandlerChain.setRoles()</code> and
     * <code>HandlerChain.getRoles()</code>. Handler instances in the
     * <code>HandlerChain</code> use this information about the SOAP actor roles
     * to process the SOAP header blocks. Note that the SOAP actor roles are
     * invariant during the processing of SOAP message through the
     * <code>HandlerChain</code>.
     *
     * @return an array of URIs for SOAP actor roles
     * @see javax.xml.rpc.handler.HandlerChain#setRoles(java.lang.String[]) HandlerChain.setRoles(java.lang.String[])
     * @see javax.xml.rpc.handler.HandlerChain#getRoles() HandlerChain.getRoles()
     */
    public String[] getRoles() {
        //TODO: Flesh this out.
        return roles;
    }

    /**
     * Set the SOAP actor roles associated with an executioni of
     * <code>CodeHandlerChain</code> and its contained <code>Handler</code>
     * instances.
     *
     * @param roles an array of <code>String</code> instances, each representing
     *              the URI for a SOAP actor role
     */
    public void setRoles( String[] roles) {
        this.roles = roles;
    }

    /**
     * if a message (or subclass) has any disposal needs, this method
     * is where it goes. Subclasses *must* call super.dispose(), and
     * be prepared to be called from the finalizer as well as earlier
     */
    public synchronized void dispose() {
        log.debug("disposing of message context");
        if(requestMessage!=null) {
            requestMessage.dispose();
            requestMessage=null;
        }
        if(responseMessage!=null) {
            responseMessage.dispose();
            responseMessage=null;
        }
    }
}
