// 
// The Waveset SPML General License 
// 
// Version 0.1, April 2003
// Copyright (C) 2003 Waveset Technologies, Inc..
// 6034 West Courtyard Drive, Suite 210, Austin, Texas 78730
// All rights reserved.
// 
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// 
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions, and the disclaimers in Sections 6
//    and 7 below.
// 
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the disclaimers in Sections 6
//    and 7 below in the documentation and/or other materials provided
//    with the distribution.
// 
// 3. The end-user documentation included with the redistribution, if
//    any, must include the following acknowledgment:
// 
//    "This product includes software developed by 
//     Waveset Technologies, Inc. (www.waveset.com)."
// 
//    Alternately, this acknowledgment may appear in the software itself, if
//    and wherever such third-party acknowledgments normally appear.
//  
// 4. The names "Waveset" and "Waveset Technologies, Inc." must not be
//    used to endorse or promote products derived from this software
//    without the prior written permission of Waveset. For written
//    permission, please contact www.waveset.com.
//  
// 5. Products derived from this software may not be called "Waveset",
//    nor may "Waveset" appear in their name, without the prior written
//    permission of Waveset.
//  
// 6. NO WARRANTY 
// 
//    THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED
//    OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//    DISCLAIMED.
// 
// 7.  LIMITATION OF LIABILITY
// 
//    IN NO EVENT SHALL THE WAVESET OR ITS LICENSORS BE LIABLE FOR ANY
//    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
//    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
//    GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
//    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
//    IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
//    OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
//    ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 
// End of Terms and Conditions
// ====================================================================
//
// Component Name: org.openspml.message.SpmlResponse
//
// Author(s): Jeff Larson
//
// Description:
//
// Base class for all SPML response objects.
//

package org.openspml.message;

import java.util.*;
import org.w3c.dom.*;

import org.openspml.util.*;

/**
 * The base class for all class' representing SPML response messages.
 */
public abstract class SpmlResponse implements Constants {

    //////////////////////////////////////////////////////////////////////
    //
    // Constants
    //
    //////////////////////////////////////////////////////////////////////
    
    //
    // ResultCode enumeration constants
    //

    /**
     * Result codes representing success.
     */
    public static final String RESULT_SUCCESS = 
    ENUM_PREFIX + "success";

    /**
     * Result code representing failure.
     */
    public static final String RESULT_FAILURE = 
    ENUM_PREFIX + "failure";

    /**
     * Result code indicating that the request is still pending.
     */
    public static final String RESULT_PENDING = 
    ENUM_PREFIX + "pending";

    //
    // ErrorCode enumeration constants
    //

    /**
     * Error code indicating a request formatting error.
     */
    public static final String ERROR_MALFORMED_REQUEST = 
    ENUM_PREFIX + "malformedRequest";

    /**
     * Error code indicating that the requested operation
     * is not supported by the server.
     */
    public static final String ERROR_UNSUPPORTED_OPERATION = 
    ENUM_PREFIX + "unsupportedOperation";

    /**
     * Error code indicating that an identifier type used
     * in the request is not supported by the server.
     */
    public static final String ERROR_UNSUPPORTED_IDENTIFIER_TYPE = 
    ENUM_PREFIX + "unsupportedIdentifierType";

    /**
     * Error code indicating that the identifier provided
     * in the request did not match an object managed by the server.
     */
    public static final String ERROR_NO_SUCH_IDENTIFIER = 
    ENUM_PREFIX + "noSuchIdentifier";

    /**
     * Error code indicating that the the identifier for
     * an extended request was not recognized.
     // ?? true, or is this used for statusRequest/cancelRequst?
     */
    public static final String ERROR_NO_SUCH_REQUEST = 
    ENUM_PREFIX + "noSuchRequest";

    /**
     * Error code indicating that a PSP specific error
     * was returned.
     */
    public static final String ERROR_CUSTOM_ERROR = 
    ENUM_PREFIX + "customError";

    //////////////////////////////////////////////////////////////////////
    //
    // Constants
    //
    //////////////////////////////////////////////////////////////////////

    /**
     * Result code.
     // !! Think about just having two boolean "failure" and "pending"
     // flags instead?
     */
    String _result;

    /**
     * Error code.
     */
    String _error;

    /**
     * Request id.
     */
    String _requestId;

    /**
     * Error message.  The specification allows only one error message.
     * PSPs that wish to return more must use operational attributes.
     * A common convention of returning the operational attribute
     * named "errorMessages" is suggested.
     */
    String _errorMessage;

    /**
     * Operational attributes.
     * This includes information that needs to be sent with the response
     * but which isn't part of the objectClass being manipulated.
     */
    List _operationalAttributes;

    //////////////////////////////////////////////////////////////////////
    //
    // Constructors
    //
    //////////////////////////////////////////////////////////////////////
    
    public SpmlResponse() {
    }

    public void setRequestId(String s) {
        _requestId = s;
    }

    public void setResult(String s) {   
        _result = s;
    }

    public void setError(String s) {
        _error = s;
    }

    public void setErrorMessage(String s) {
        _errorMessage = s;

        // should this track implicitly?
        if (_errorMessage != null)
            _result = RESULT_FAILURE;
    }

    public void setErrorMessage(Throwable t) {
        // localization?
        setErrorMessage(t.toString());
    }

    /**
     * Add an extended error message as an operational attribute.
     * This is not part of the standard, but is commonly convenient.
     */
    public void addExtendedErrorMessage(String s) {

        if (s != null) {
            List msgs = (List)getOperationalAttribute("errorMessages");
            if (msgs == null) {
                msgs = new ArrayList();
                setOperationalAttribute("errorMessages", msgs);
            }
            msgs.add(s);
        }
    }

    public void setOperationalAttributes(List l) {
        _operationalAttributes = l;
    }

    /**
     * Sets the list of attributes from a map of name/value pairs.
     */
    public void setOperationalAttributes(Map src) {
        _operationalAttributes = Attribute.getAttributes(src);
    }

    public void setOperationalAttribute(Attribute a) {
        if (_operationalAttributes == null)
            _operationalAttributes = new ArrayList();
        _operationalAttributes.add(a);
    }
    
    public void setOperationalAttribute(String name, Object value) {
        setOperationalAttribute(new Attribute(name, value));
    }

    public void removeOperationalAttribute(String name) {

        Attribute a = Attribute.getAttribute(_operationalAttributes, name);
        if (a != null)
            _operationalAttributes.remove(a);   
    }

    //////////////////////////////////////////////////////////////////////
    //
    // Accessors
    //
    //////////////////////////////////////////////////////////////////////

    public String getResult() {
        return _result;
    }

    public boolean isFailure() {
        return RESULT_FAILURE.equals(_result);
    }

    public String getError() {
        return _error;
    }

    public String getErrorMessage() {
        return _errorMessage;
    }

    public String getRequestId() {
        return _requestId;
    }

    public List getExtendedErrorMessages() {
        List msgs = null;
        Object o = getOperationalAttribute("errorMessages");
        if (o instanceof List)
            msgs = (List)o;
        return msgs;
    }

    public List getOperationalAttributes() {
        return _operationalAttributes;
    }

    public Attribute getOperationalAttribute(String name) {
	return Attribute.getAttribute(_operationalAttributes, name);
    }

    public Object getOperationalAttributeValue(String name) {
	return Attribute.getAttributeValue(_operationalAttributes, name);
    }

    //////////////////////////////////////////////////////////////////////
    //
    // Utilities
    //
    //////////////////////////////////////////////////////////////////////

    /**
     * Examine the response for errors.  If errors are found, format
     * them into a single message and throw an exception containing the
     * message.
     */
    public void throwErrors() throws SpmlException {

        // consolodate everything into one exception
        // for more control over rendering, will need SpmlResponse      
        // or ExceptionWrapper to support arbitrary lists of messages
        StringBuffer buf = null;

        if (isFailure()) {
            buf = new StringBuffer();
            String error = getError();
            buf.append("Failure: " );
            if (error != null)
                buf.append(error);
            else 
                buf.append("unspecified error");
            buf.append("\n");
        }

        String msg = getErrorMessage();
        if (msg != null) {
            if (buf == null)
                buf = new StringBuffer();
            if (buf.length() > 0)
                buf.append("\n");
            buf.append(msg);
        }

        List errors = getExtendedErrorMessages();
        if (errors != null) {
            if (buf == null)
                buf = new StringBuffer();
            for (int i = 0 ; i < errors.size() ; i++) {
                msg = (String)errors.get(i);
                if (buf.length() > 0)
                    buf.append("\n");
                buf.append(msg);
            }
        }

        if (buf != null)
            throw new SpmlException(buf.toString());
    }

    //////////////////////////////////////////////////////////////////////
    //
    // XML
    //
    // We have no toXml method, rather the base class calls us 
    // to fill in our parts.
    //
    //////////////////////////////////////////////////////////////////////

    abstract String getElementName();

    void addSubclassAttributes(SpmlBuffer b) {
    }

    void addSubclassElements(SpmlBuffer b) {
    }

    public String toXml() {
        SpmlBuffer b = new SpmlBuffer();
        toXml(b);
        return b.toString();
    }

    void toXml(SpmlBuffer b) {

        String tag = getElementName();
        b.addOpenStartTag(tag);

        if (_requestId != null)
            b.addAttribute("requestID", _requestId);

        if (_result != null)
            b.addAttribute("result", _result);
	else {
	    // client's often do not set success only failure so unless
	    // otherwise directed, assume success
            b.addAttribute("result", RESULT_SUCCESS);
	}

        if (_error != null)
            b.addAttribute("error", _error);

        addSubclassAttributes(b);

        b.closeStartTag();
        b.incIndent();

        Attribute.toXml(b, "operationalAttributes", 
                        _operationalAttributes);

        if (_errorMessage != null) {
            b.addStartTag("errorMessage", false);
            b.addContent(_errorMessage);
            b.addEndTag("errorMessage", false);
        }

        addSubclassElements(b);

        b.decIndent();
        b.addEndTag(tag);
    }

    void parseXml(XmlElement e) {

        _requestId = e.getAttribute("requestID");
        _result = e.getAttribute("result");
        _error = e.getAttribute("error");

	for (XmlElement child = e.getChildElement() ; 
	     child != null ; child = child.next()) {
	    String tag = child.getLocalName();
 
            if (tag.equals("operationalAttributes"))
                _operationalAttributes = Attribute.parseList(child);

            else if (tag.equals("errorMessage")) {
                setErrorMessage(child.getContent());
            }

            else {
                // since these are supposed to be ordered,
                // can stop now
                break;
            }
        }
    }

    /**
     * Static method to parse the XML for one of the SpmlResponse
     * classes and return the correct object.
     *
     * Will accept a complete <soap:Envelope> or a disembodied
     * SpmlResponse element.
     */
    public static SpmlResponse parseResponse(String xml) 
        throws SpmlException {

        SpmlResponse response = null;

        Document d = XmlParser.parse(xml, false);
        XmlElement envelope = new XmlElement(d.getDocumentElement());
        XmlElement re = null;

        if (!envelope.getLocalName().equals("Envelope")) {
            // assume it is a standalone response element
            re = envelope;
        }
        else {
            XmlElement body = envelope.getChildElement("Body");
            if (body != null) {
                re = body.getChildElement();
		// may have a <soap:Fault> element
		String tag = re.getLocalName();
		if (tag.equals("Fault")) {
		    XmlElement fs = re.getChildElement("faultstring");
		    if (fs != null)
			throw new SpmlException(fs.getContent());

		    fs = re.getChildElement("faultcode");
		    if (fs != null)
			throw new SpmlException(fs.getContent());

		    throw new SpmlException("<soap:Fault> received");
		}
	    }
        }

        if (re != null) 
            response = SpmlResponse.parseResponse(re);

        return response;
    }

    /**
     * Static method to parse any root SPML response.
     */
    public static SpmlResponse parseResponse(Element domel) {

        return parseResponse(new XmlElement(domel));
    }

    /**
     * Static method to parse any root SPML response.
     */
    public static SpmlResponse parseResponse(XmlElement e) {

	SpmlResponse res = null;

	String name = e.getLocalName();

	if (name.equals(BatchResponse.ELEMENT))
	    res = new BatchResponse(e);

	else if (name.equals(StatusResponse.ELEMENT))
	    res = new StatusResponse(e);

	else if (name.equals(CancelResponse.ELEMENT))
	    res = new CancelResponse(e);

	else if (name.equals(SchemaResponse.ELEMENT))
	    res = new SchemaResponse(e);

	else if (name.equals(AddResponse.ELEMENT))
	    res = new AddResponse(e);

	else if (name.equals(ModifyResponse.ELEMENT))
	    res = new ModifyResponse(e);

	else if (name.equals(DeleteResponse.ELEMENT))
	    res = new DeleteResponse(e);

	else if (name.equals(SearchResponse.ELEMENT))
	    res = new SearchResponse(e);

	else if (name.equals(ExtendedResponse.ELEMENT))
	    res = new ExtendedResponse(e);

	return res;
    }

    /**
     * Static method to create an empty response object
     * that corresponds to an request element.
     * This is used in cases where we couldn't parse the
     * request, but the response we send back with the error messages
     * is expected to match the request.
     */
    public static SpmlResponse createResponseForRequest(Element domel) {

        return createResponseForRequest(new XmlElement(domel));
    }

    /**
     * Static method to create an empty response object
     * that corresponds to an request element.
     * This is used in cases where we couldn't parse the
     * request, but the response we send back with the error messages
     * is expected to match the request.
     */
    public static SpmlResponse createResponseForRequest(XmlElement e) {

	SpmlResponse res = null;

	String name = e.getLocalName();

	if (name.equals(BatchResponse.ELEMENT))
	    res = new BatchResponse();

	else if (name.equals(StatusResponse.ELEMENT))
	    res = new StatusResponse();

	else if (name.equals(CancelResponse.ELEMENT))
	    res = new CancelResponse();

	else if (name.equals(SchemaResponse.ELEMENT))
	    res = new SchemaResponse();

	else if (name.equals(AddResponse.ELEMENT))
	    res = new AddResponse();

	else if (name.equals(ModifyResponse.ELEMENT))
	    res = new ModifyResponse();

	else if (name.equals(DeleteResponse.ELEMENT))
	    res = new DeleteResponse();

	else if (name.equals(SearchResponse.ELEMENT))
	    res = new SearchResponse();

	else if (name.equals(ExtendedResponse.ELEMENT))
	    res = new ExtendedResponse();

        else {
            // need something as a fallback
            // UPDATE: This should return a standard SOAP fault
	    res = new BatchResponse();
        }

	return res;
    }

    /**
     * Handy function to call in the debugger.
     */
    public void dumpFile(String name) {

        Util.writeFileQuietly(name, toXml());
    }

}
