/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.axis2.jaxws.provider.soapbinding.tests;


import java.io.ByteArrayInputStream;
import java.util.Iterator;

import javax.xml.namespace.QName;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.DetailEntry;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.Node;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPConstants;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPFault;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import javax.xml.ws.soap.SOAPFaultException;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import org.apache.axis2.jaxws.framework.AbstractTestCase;
import org.apache.axis2.jaxws.polymorphic.shape.tests.PolymorphicTests;
import org.apache.axis2.jaxws.provider.soapbinding.soapmsg.SoapMessageProvider;

/**
 * Tests Dispatch<SOAPMessage> client and a Provider<SOAPMessage> service.
 * The client and service interaction tests various xml and attachment scenarios
 *
 */
public class SoapMessageProviderTests extends AbstractTestCase {
	private String endpointUrl = "http://localhost:6060/axis2/services/SoapMessageProviderService.SoapMessageProviderPort";
	private QName serviceName = new QName("http://soapmsg.soapbinding.provider.jaxws.axis2.apache.org", "SOAPBindingSoapMessageProviderService");
	private QName portName =  new QName("http://soapmsg.soapbinding.provider.jaxws.axis2.apache.org", "SoapMessageProviderPort");

	private String reqMsgStart = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
	"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body>";
	;

	private String reqMsgEnd = "</soap:Body></soap:Envelope>";

	private String XML_INVOKE = "<ns2:invokeOp xmlns:ns2=\"http://org.test.soapmessage\"><invoke_str>" + 
	SoapMessageProvider.XML_REQUEST +
	"</invoke_str></ns2:invokeOp>";
	private String EMPTYBODY_INVOKE = "<ns2:invokeOp xmlns:ns2=\"http://org.test.soapmessage\"><invoke_str>" + 
	SoapMessageProvider.XML_EMPTYBODY_REQUEST +
	"</invoke_str></ns2:invokeOp>";
	private String CHECKHEADERS_INVOKE = "<ns2:invokeOp xmlns:ns2=\"http://org.test.soapmessage\"><invoke_str>" + 
	SoapMessageProvider.XML_CHECKHEADERS_REQUEST +
	"</invoke_str></ns2:invokeOp>";
	private String ATTACHMENT_INVOKE = "<ns2:invokeOp xmlns:ns2=\"http://org.test.soapmessage\"><invoke_str>" + 
	SoapMessageProvider.XML_ATTACHMENT_REQUEST +
	"</invoke_str></ns2:invokeOp>";
	private String MTOM_INVOKE = "<ns2:invokeOp xmlns:ns2=\"http://org.test.soapmessage\"><invoke_str>" + 
	SoapMessageProvider.XML_MTOM_REQUEST +
	"</invoke_str>" + 
	SoapMessageProvider.MTOM_REF +
	"</ns2:invokeOp>";
	private String SWAREF_INVOKE = "<ns2:invokeOp xmlns:ns2=\"http://org.test.soapmessage\"><invoke_str>" + 
	SoapMessageProvider.XML_SWAREF_REQUEST +
	"</invoke_str>" + 
	SoapMessageProvider.SWAREF_REF +
	"</ns2:invokeOp>";   
	private String XML_FAULT_INVOKE = "<ns2:invokeOp xmlns:ns2=\"http://org.test.soapmessage\"><invoke_str>" + 
	SoapMessageProvider.XML_FAULT_REQUEST +
	"</invoke_str></ns2:invokeOp>";
	private String XML_WSE_INVOKE = "<ns2:invokeOp xmlns:ns2=\"http://org.test.soapmessage\"><invoke_str>" + 
	SoapMessageProvider.XML_WSE_REQUEST +
	"</invoke_str></ns2:invokeOp>";
	private String XML_SOAP12_FAULT_INVOKE = "<ns2:invokeOp xmlns:ns2=\"http://org.test.soapmessage\"><invoke_str>" + 
	SoapMessageProvider.XML_SOAP12_FAULT_REQUEST +
	"</invoke_str></ns2:invokeOp>";  
	private String XML_SOAP12_RESPONSE_INVOKE = "<ns2:invokeOp xmlns:ns2=\"http://org.test.soapmessage\"><invoke_str>" + 
	SoapMessageProvider.XML_SOAP12_RESPONSE +
	"</invoke_str></ns2:invokeOp>";
	
	public static Test suite() {
        return getTestSetup(new TestSuite(SoapMessageProviderTests.class));
    }
	/**
	 * Sends an SOAPMessage containing only xml data 
	 * Provider will throw a Fault
	 */
	public void testProviderSOAPMessageSOAPFault() throws Exception {

		// Create the dispatch
		Dispatch<SOAPMessage> dispatch = createDispatch();

		// Create the SOAPMessage
		String msg = reqMsgStart + XML_FAULT_INVOKE + reqMsgEnd;
		MessageFactory factory = MessageFactory.newInstance();
		SOAPMessage request = factory.createMessage(null, 
				new ByteArrayInputStream(msg.getBytes()));

		// Test the transport headers by sending a content description
		request.setContentDescription(SoapMessageProvider.XML_FAULT_REQUEST);

		try {
			// Dispatch
			System.out.println(">> Invoking SOAPMessageProviderDispatch");
			SOAPMessage response = dispatch.invoke(request);
			assertTrue("Expected failure", false);
		} catch (SOAPFaultException e) {
			// Okay
			SOAPFault fault = e.getFault();
			assertTrue(fault != null);
			assertTrue(fault.getFaultString().equals("sample fault"));
			QName expectedFaultCode = new QName(SOAPConstants.URI_NS_SOAP_1_1_ENVELOPE, "Client"); 
			assertTrue(fault.getFaultCodeAsQName().equals(expectedFaultCode));
			assertTrue(fault.getDetail() != null);
			DetailEntry de = (DetailEntry) fault.getDetail().getDetailEntries().next();
			assertTrue(de != null);
			assertTrue(de.getLocalName().equals("detailEntry"));
			assertTrue(de.getValue().equals("sample detail"));
			assertTrue(fault.getFaultActor().equals("sample actor"));
		}    

		// Try a second time
		try {
			// Dispatch
			System.out.println(">> Invoking SOAPMessageProviderDispatch");
			SOAPMessage response = dispatch.invoke(request);
			assertTrue("Expected failure", false);
		} catch (SOAPFaultException e) {
			// Okay
			SOAPFault fault = e.getFault();
			assertTrue(fault != null);
			assertTrue(fault.getFaultString().equals("sample fault"));
			QName expectedFaultCode = new QName(SOAPConstants.URI_NS_SOAP_1_1_ENVELOPE, "Client"); 
			assertTrue(fault.getFaultCodeAsQName().equals(expectedFaultCode));
			assertTrue(fault.getDetail() != null);
			DetailEntry de = (DetailEntry) fault.getDetail().getDetailEntries().next();
			assertTrue(de != null);
			assertTrue(de.getLocalName().equals("detailEntry"));
			assertTrue(de.getValue().equals("sample detail"));
			assertTrue(fault.getFaultActor().equals("sample actor"));
		}    
	}


	/**
	 * Sends an SOAPMessage containing only xml data 
	 * Provider will throw a generic WebServicesException
	 */
	public void testProviderSOAPMessageWebServiceException() throws Exception {

		// Create the dispatch
		Dispatch<SOAPMessage> dispatch = createDispatch();

		// Create the SOAPMessage
		String msg = reqMsgStart + XML_WSE_INVOKE + reqMsgEnd;
		MessageFactory factory = MessageFactory.newInstance();
		SOAPMessage request = factory.createMessage(null, 
				new ByteArrayInputStream(msg.getBytes()));

		// Test the transport headers by sending a content description
		request.setContentDescription(SoapMessageProvider.XML_WSE_REQUEST);

		try {
			// Dispatch
			System.out.println(">> Invoking SOAPMessageProviderDispatch");
			SOAPMessage response = dispatch.invoke(request);
			assertTrue("Expected failure", false);
		} catch (SOAPFaultException e) {
			// Okay...SOAPFaultException should be thrown
			SOAPFault fault = e.getFault();
			assertTrue(fault != null);
			assertTrue(fault.getFaultString().equals("A WSE was thrown"));
		}   

		// Try a second time
		try {
			// Dispatch
			System.out.println(">> Invoking SOAPMessageProviderDispatch");
			SOAPMessage response = dispatch.invoke(request);
			assertTrue("Expected failure", false);
		} catch (SOAPFaultException e) {
			// Okay...SOAPFaultException should be thrown
			SOAPFault fault = e.getFault();
			assertTrue(fault != null);
			assertTrue(fault.getFaultString().equals("A WSE was thrown"));
		} 
	}


	/**
	 * Sends an SOAPMessage containing xml data and raw attachments to the web service.  
	 * Receives a response containing xml data and the same raw attachments.
	 */

	public void testProviderSOAPMessageRawAttachment(){
		// Raw Attachments are attachments that are not referenced in the xml with MTOM or SWARef.
		// Currently there is no support in Axis 2 for these kinds of attachments.
		// The belief is that most customers will use MTOM.  Some legacy customers will use SWARef.
		// Raw Attachments may be so old that no customers need this behavior.
		try{       
			// Create the dispatch
			Dispatch<SOAPMessage> dispatch = createDispatch();

			// Create the SOAPMessage
			String msg = reqMsgStart + ATTACHMENT_INVOKE + reqMsgEnd;
			MessageFactory factory = MessageFactory.newInstance();
			SOAPMessage request = factory.createMessage(null, 
					new ByteArrayInputStream(msg.getBytes()));

			// Add the Attachment
			AttachmentPart ap = request.createAttachmentPart(SoapMessageProvider.TEXT_XML_ATTACHMENT, "text/xml");
			ap.setContentId(SoapMessageProvider.ID);
			request.addAttachmentPart(ap);

			// Dispatch
			System.out.println(">> Invoking SOAPMessageProviderDispatch");
			SOAPMessage response = dispatch.invoke(request);

			// Check assertions and get the data element
			SOAPElement dataElement = assertResponseXML(response, SoapMessageProvider.XML_ATTACHMENT_RESPONSE);
			assertTrue(countAttachments(response) == 1);

			// Get the Attachment
			AttachmentPart attachmentPart = (AttachmentPart) response.getAttachments().next();

			// Check the attachment
			StreamSource contentSS = (StreamSource) attachmentPart.getContent();
			String content = SoapMessageProvider.getAsString(contentSS);
			assertTrue(content != null);
			assertTrue(content.contains(SoapMessageProvider.TEXT_XML_ATTACHMENT));

			// Print out the response
			System.out.println(">> Response [" + response.toString() + "]");


			// Try a second time
			// Dispatch
			System.out.println(">> Invoking SOAPMessageProviderDispatch");
			response = dispatch.invoke(request);

			// Check assertions and get the data element
			dataElement = assertResponseXML(response, SoapMessageProvider.XML_ATTACHMENT_RESPONSE);
			assertTrue(countAttachments(response) == 1);

			// Get the Attachment
			attachmentPart = (AttachmentPart) response.getAttachments().next();

			// Check the attachment
			contentSS = (StreamSource) attachmentPart.getContent();
			content = SoapMessageProvider.getAsString(contentSS);
			assertTrue(content != null);
			assertTrue(content.contains(SoapMessageProvider.TEXT_XML_ATTACHMENT));

			// Print out the response
			System.out.println(">> Response [" + response.toString() + "]");

		}catch(Exception e){
			e.printStackTrace();
			fail("Caught exception " + e);
		}

	}

	/**
	 * Sends an SOAPMessage containing xml data and mtom attachment.  
	 * Receives a response containing xml data and the mtom attachment.
	 */
	public void testProviderSOAPMessageMTOM(){
		try{       
			// Create the dispatch
			Dispatch<SOAPMessage> dispatch = createDispatch();

			// MTOM should be automatically detected.  There is no need to set it
			//Binding binding = dispatch.getBinding();
			//SOAPBinding soapBinding = (SOAPBinding) binding;
			//soapBinding.setMTOMEnabled(true);

			// Create the SOAPMessage
			String msg = reqMsgStart + MTOM_INVOKE + reqMsgEnd;
			MessageFactory factory = MessageFactory.newInstance();
			SOAPMessage request = factory.createMessage(null, 
					new ByteArrayInputStream(msg.getBytes()));

			// Add the Attachment
			AttachmentPart ap = request.createAttachmentPart(SoapMessageProvider.TEXT_XML_ATTACHMENT, "text/xml");
			ap.setContentId(SoapMessageProvider.ID);
			request.addAttachmentPart(ap);

			// Dispatch
			System.out.println(">> Invoking SOAPMessageProviderDispatch");
			SOAPMessage response = dispatch.invoke(request);

			// Check assertions and get the data element
			SOAPElement dataElement = assertResponseXML(response, SoapMessageProvider.XML_MTOM_RESPONSE);
			assertTrue(countAttachments(response) == 1);

			// Get the Attachment
			AttachmentPart attachmentPart = (AttachmentPart) response.getAttachments().next();

			// Check the attachment
			StreamSource contentSS = (StreamSource) attachmentPart.getContent();
			String content = SoapMessageProvider.getAsString(contentSS);
			assertTrue(content != null);
			assertTrue(content.contains(SoapMessageProvider.TEXT_XML_ATTACHMENT));

			// Print out the response
			System.out.println(">> Response [" + response.toString() + "]");


			// Try a second time
			// Dispatch
			System.out.println(">> Invoking SOAPMessageProviderDispatch");
			response = dispatch.invoke(request);

			// Check assertions and get the data element
			dataElement = assertResponseXML(response, SoapMessageProvider.XML_MTOM_RESPONSE);
			assertTrue(countAttachments(response) == 1);

			// Get the Attachment
			attachmentPart = (AttachmentPart) response.getAttachments().next();

			// Check the attachment
			contentSS = (StreamSource) attachmentPart.getContent();
			content = SoapMessageProvider.getAsString(contentSS);
			assertTrue(content != null);
			assertTrue(content.contains(SoapMessageProvider.TEXT_XML_ATTACHMENT));

			// Print out the response
			System.out.println(">> Response [" + response.toString() + "]");

		}catch(Exception e){
			e.printStackTrace();
			fail("Caught exception " + e);
		}

	}

	/**
	 * Sends an SOAPMessage containing xml data and a swaref attachment to the web service.  
	 * Receives a response containing xml data and the swaref attachment attachment.
	 */
	public void testProviderSOAPMessageSWARef(){
		try{       
			// Create the dispatch
			Dispatch<SOAPMessage> dispatch = createDispatch();

			// Create the SOAPMessage
			String msg = reqMsgStart + SWAREF_INVOKE + reqMsgEnd;
			MessageFactory factory = MessageFactory.newInstance();
			SOAPMessage request = factory.createMessage(null, 
					new ByteArrayInputStream(msg.getBytes()));

			// Add the Attachment
			AttachmentPart ap = request.createAttachmentPart(SoapMessageProvider.TEXT_XML_ATTACHMENT, "text/xml");
			ap.setContentId(SoapMessageProvider.ID);
			request.addAttachmentPart(ap);

			// Dispatch
			System.out.println(">> Invoking SOAPMessageProviderDispatch");
			SOAPMessage response = dispatch.invoke(request);

			// Check assertions and get the data element
			SOAPElement dataElement = assertResponseXML(response, SoapMessageProvider.XML_SWAREF_RESPONSE);
			assertTrue(countAttachments(response) == 1);

			// Get the Attachment
			AttachmentPart attachmentPart = (AttachmentPart) response.getAttachments().next();

			// Check the attachment
			StreamSource contentSS = (StreamSource) attachmentPart.getContent();
			String content = SoapMessageProvider.getAsString(contentSS);
			assertTrue(content != null);
			assertTrue(content.contains(SoapMessageProvider.TEXT_XML_ATTACHMENT));
			assertEquals(SoapMessageProvider.ID, attachmentPart.getContentId());

			// Print out the response
			System.out.println(">> Response [" + response.toString() + "]");



			// Try a second time
			// Dispatch
			System.out.println(">> Invoking SOAPMessageProviderDispatch");
			response = dispatch.invoke(request);

			// Check assertions and get the data element
			dataElement = assertResponseXML(response, SoapMessageProvider.XML_SWAREF_RESPONSE);
			assertTrue(countAttachments(response) == 1);

			// Get the Attachment
			attachmentPart = (AttachmentPart) response.getAttachments().next();

			// Check the attachment
			contentSS = (StreamSource) attachmentPart.getContent();
			content = SoapMessageProvider.getAsString(contentSS);
			assertTrue(content != null);
			assertTrue(content.contains(SoapMessageProvider.TEXT_XML_ATTACHMENT));
			assertEquals(SoapMessageProvider.ID, attachmentPart.getContentId());

			// Print out the response
			System.out.println(">> Response [" + response.toString() + "]");

		}catch(Exception e){
			e.printStackTrace();
			fail("Caught exception " + e);
		}

	}

	/* This is a negative test case for a Provider that has NO SOAPBinding restriction
	 * Dispatch will send a SOAP11 request and Provider will send a SOAP12 Response.
	 */
	public void testSoap11RequestWithSoap12Response(){
		SOAPMessage request = null;
		Dispatch<SOAPMessage> dispatch = null;
		try{
			// Create the dispatch
			dispatch = createDispatch();
			// Create the SOAPMessage
			String msg = reqMsgStart + XML_SOAP12_RESPONSE_INVOKE + reqMsgEnd;
			MessageFactory factory = MessageFactory.newInstance();
			request = factory.createMessage(null, 
					new ByteArrayInputStream(msg.getBytes()));
		}catch(Exception e){
			e.printStackTrace();
			fail("Caught Exception "+e);
		}
		try{
			SOAPMessage response = dispatch.invoke(request);
			assertTrue("Expecting Failure", false);
		}catch(SOAPFaultException e){
			SOAPFault fault = e.getFault();
			assertTrue(fault != null);
			assertTrue(fault.getFaultString().equals("Request SOAP message protocol is version 1.1, but Response SOAP message is configured for SOAP 1.2.  This is not supported."));
		}
		
	}
	/**
	 * @return
	 * @throws Exception
	 */
	private Dispatch<SOAPMessage> createDispatch() throws Exception {


		Service svc = Service.create(serviceName);
		svc.addPort(portName,null, endpointUrl);
		Dispatch<SOAPMessage> dispatch = 
			svc.createDispatch(portName, SOAPMessage.class, Service.Mode.MESSAGE);
		return dispatch;
	}

	/**
	 * @return
	 * @throws Exception
	 */
	private Dispatch<String> createStringDispatch() throws Exception {


		Service svc = Service.create(serviceName);
		svc.addPort(portName,null, endpointUrl);
		Dispatch<String> dispatch = 
			svc.createDispatch(portName, String.class, Service.Mode.PAYLOAD);
		return dispatch;
	}

	/**
	 * Common assertion checking of the response
	 * @param msg
	 * @param expectedText
	 * @return SOAPElement representing the data element
	 */
	private SOAPElement assertResponseXML(SOAPMessage msg, String expectedText) throws Exception {
		assertTrue(msg != null);
		SOAPBody body = msg.getSOAPBody();
		assertTrue(body != null);

		Node invokeElement = (Node) body.getFirstChild();
		assertTrue(invokeElement instanceof SOAPElement);
		assertEquals(SoapMessageProvider.RESPONSE_NAME, invokeElement.getLocalName());

		Node dataElement = (Node) invokeElement.getFirstChild();
		assertTrue(dataElement instanceof SOAPElement);
		assertEquals(SoapMessageProvider.RESPONSE_DATA_NAME, dataElement.getLocalName());

		// TODO AXIS2 SAAJ should (but does not) support the getTextContent();
		// String text = dataElement.getTextContent();
		String text = dataElement.getValue();
		assertEquals("Found ("+ text + ") but expected (" + expectedText + ")", expectedText, text);

		return (SOAPElement) dataElement;
	}

	/**
	 * Count Attachments
	 * @param msg
	 * @return
	 */
	private int countAttachments(SOAPMessage msg) {
		Iterator it = msg.getAttachments();
		int count = 0;
		assertTrue(it != null);
		while (it.hasNext()) {
			it.next();
			count++;
		}
		return count;
	}
}

