// 
// 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.client.SOAPClient
//
// Author(s): Jeff Larson
//
// Description:
//
// An adaptation of some Apache SOAP code, primarily SOAPHTTPConnection 
// that provides a simpler interface for message-oriented protocols.
// Avoids the unfortunate marshaling/unmarshaling of the Envelope if you
// just want to send formatted XML.
//
// This was implemented against the 2.2 version.
// 

package org.openspml.client;

import java.io.*;
import java.net.*;
import java.util.*;
import org.w3c.dom.*;
import org.apache.soap.util.net.*;
import org.apache.soap.util.xml.*;
import org.apache.soap.util.mime.*;
import org.apache.soap.*;
import org.apache.soap.encoding.*;
import org.apache.soap.encoding.soapenc.Base64;
import org.apache.soap.transport.*;
import org.apache.soap.rpc.*;
import org.apache.soap.Constants;
import org.apache.soap.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;

/**
 * A class providing a simplfied interface for making message-oriented
 * SOAP requests using the Apache 2.2 SOAP library.
 */
public class SOAPClient {

    /**
     * Prefix we put on the desired message XML.
     */
    static final String ENVELOPE = 
    "<s:Envelope xmlns:s='http://schemas.xmlsoap.org/soap/envelope/'>\n";

    static final String HEADSTART = 
    "<s:Header>\n";

    static final String HEADEND = 
    "</s:Header>\n";

    static final String BODY = 
    "<s:Body>\n";

    static final String BODYOPEN = 
    "<s:Body";

    static final String SUFFIX = "</s:Body>\n</s:Envelope>\n";

    /**
     * Not sure what this does.  Examles just create one without args,
     * it has to be passed into a few things.
     */
    private SOAPContext _context = new SOAPContext();

    /**
     * Set as a side effect of a sucessfull send() call.  Used
     * by receive() to fetch the response.
     */
    private SOAPContext _responseContext;

    /**
     * Set as a side effect of a sucessfull send() call.  Used
     * by receive() to fetch the response.
     */
    private BufferedReader _responseReader;

    /**
     * Optional monitor that may be registered to receive notification
     * of SOAP traffic.
     */
    SOAPMonitor _monitor;

    /**
     * Optional blob to include in the soap header.
     */
    String _header;

    /**
     * Optional body attribute.
     * Assume this is alread well formed XML syntax for simplicity.
     */
    String _bodyAttributes;

    /**
     * Optional connection settings.  Not using these, but kept them
     * around just in case.
     */
    private String _httpProxyHost;
    private int    _httpProxyPort = 80;
    private int    _timeout;
    private boolean _maintainSession = true;
    private String _cookieHeader;
    private String _cookieHeader2;
    private int    _outputBufferSize = HTTPUtils.DEFAULT_OUTPUT_BUFFER_SIZE;

    public void setMonitor(SOAPMonitor m) {

        // just one for now, ok for the client
        _monitor = m;
    }

    public void setHeader(String s) {
	_header = s;
    }

    public void setBodyAttributes(String s) {
	_bodyAttributes = s;
    }

    /**
     * Send a SOAP message already rendered as XML wrapping it 
     * in a suitable Envelope and Body.
     */
    public void send(URL url, String action, String message) 
	throws SOAPException {

	// set as a side effect
	_responseContext = null;
	_responseReader = null;

	try {
	    Hashtable headers = new Hashtable();

	    // assume this isn't wrapped in a <Body> but could 
	    // allow that too
	    StringBuffer b = new StringBuffer();
	    b.append(ENVELOPE);
	    if (_header != null) {
		b.append(HEADSTART);
		b.append(_header);
		b.append(HEADEND);
	    }
	    b.append(BODYOPEN);
	    if (_bodyAttributes != null) {
		b.append(" ");
		b.append(_bodyAttributes);
	    }
	    b.append(">\n");
	    b.append(message);
	    b.append(SUFFIX);

	    String payload = b.toString();

            // would be nice to include HTTP headers?
            if (_monitor != null)
                _monitor.send(payload);

	    if (_maintainSession) {
		// if there is saved cookie headers, put them in to the request
		if (_cookieHeader2 != null) { // RFC 2965 header
		    headers.put ("Cookie2", _cookieHeader2);
		} 
		if (_cookieHeader != null) { // RFC 2109 header
		    headers.put ("Cookie", _cookieHeader);
		}
	    }

	    headers.put (Constants.HEADER_SOAP_ACTION, 
			 (action != null) ? ('\"' + action + '\"') : "");

	    TransportMessage response;
	    try {
		TransportMessage msg = 
		    new TransportMessage(payload, _context, headers);
		msg.save();
		response = HTTPUtils.post (url, msg, _timeout, 
					   _httpProxyHost, _httpProxyPort,
					   _outputBufferSize);
	    } catch (MessagingException me) {
		throw new IOException ("Failed to encode mime multipart: " + me);
	    } catch (UnsupportedEncodingException uee) {
		throw new IOException ("Failed to encode mime multipart: " + uee);
	    }

	    Reader envReader = response.getEnvelopeReader();
	    if (envReader != null)
		_responseReader = new BufferedReader(envReader);

	    _responseContext = response.getSOAPContext();
	    Hashtable responseHeaders = response.getHeaders();

	    if (_maintainSession) {
		// look for Set-Cookie2 and Set-Cookie headers and save them after
		// stripping everything after the ';' (i.e., ignore all cookie attrs).
		// Only update my state iff the header is there .. otherwise
		// leave the current 
		String hdr;
        
		hdr = (String) responseHeaders.get ("Set-Cookie2");
		if (hdr != null) {
		    _cookieHeader2 = hdr;
		    int index = _cookieHeader2.indexOf (';');
		    if (index != -1) {
			_cookieHeader2 = _cookieHeader2.substring (0, index);
		    }
		}

		hdr = (String) responseHeaders.get ("Set-Cookie");
		if (hdr != null) {
		    _cookieHeader = hdr;
		    int index = _cookieHeader.indexOf (';');
		    if (index != -1) {
			_cookieHeader = _cookieHeader.substring (0, index);
		    }
		}
	    }
	} catch (IllegalArgumentException e) {
	    throw new SOAPException (Constants.FAULT_CODE_CLIENT, e.getMessage(), e);
	} catch (MessagingException e) {
	    throw new SOAPException (Constants.FAULT_CODE_CLIENT, e.getMessage(), e);
	} catch (IOException e) {
	    throw new SOAPException (Constants.FAULT_CODE_CLIENT, e.getMessage(), e);
	}
    }

    /**
     * Retrieve a response to a previous <code>send</code> call and
     * return it as a string of XML.
     */
    public String receive()
	throws SOAPException, MessagingException, IOException {

	String payload = null;

	if (_responseContext != null && _responseReader != null) {

	    MimeBodyPart rootPart = _responseContext.getRootPart();
	    if (rootPart.isMimeType("text/*")) {
		// Get the input stream to read the response envelope from.
		payload = IOUtils.getStringFromReader(_responseReader);
	    }

	    // Check Content-Type of root part of response to see if it's
	    // consistent with a SOAP envelope (text/xml).
	    if (!rootPart.isMimeType(Constants.HEADERVAL_CONTENT_TYPE)) {
		throw new SOAPException(Constants.FAULT_CODE_PROTOCOL,
					"Unsupported response content type \"" +
					rootPart.getContentType() + "\", must be: \"" +
					Constants.HEADERVAL_CONTENT_TYPE + "\"." +
					(payload == null ? "" : " Response was:\n" + payload));
	    }

	}

        if (_monitor != null)
            _monitor.receive(payload);

	return payload;
    }

}
