/**
 * 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.camel.component.cxf;

import org.w3c.dom.Element;
import org.w3c.dom.Text;

import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.builder.xml.XPathBuilder;
import org.apache.camel.impl.DefaultExchange;
import org.apache.camel.test.AvailablePortFinder;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.apache.commons.lang.StringUtils;
import org.apache.cxf.binding.soap.SoapHeader;
import org.junit.Test;

public class CxfConsumerPayloadXPathTest extends CamelTestSupport {
    
    public static final String HEADER_SIZE = "tstsize";

    @Test
    public void size1XPathStringResultTest() throws Exception {
        simpleTest(1, new TestRouteWithXPathStringResultBuilder());
    }
    
    @Test
    public void size100XPathStringResultTest() throws Exception {
        simpleTest(100, new TestRouteWithXPathStringResultBuilder());
    }
    
    @Test
    public void size1000XPathStringResultTest() throws Exception {
        simpleTest(1000, new TestRouteWithXPathStringResultBuilder());
    }

    @Test
    public void size10000XPathStringResultTest() throws Exception {
        simpleTest(10000, new TestRouteWithXPathStringResultBuilder());
    }
    
    @Test
    public void size1XPathTest() throws Exception {
        simpleTest(1, new TestRouteWithXPathBuilder());
    }
    
    @Test
    public void size100XPathTest() throws Exception {
        simpleTest(100, new TestRouteWithXPathBuilder());
    }
    
    @Test
    public void size1000XPathTest() throws Exception {
        simpleTest(1000, new TestRouteWithXPathBuilder());
    }
    
    @Test
    public void size10000XPathTest() throws Exception {
        simpleTest(10000, new TestRouteWithXPathBuilder());
    }  
    
    //the textnode appears to have siblings!
    @Test
    public void size10000DomTest() throws Exception {
        simpleTest(10000, new TestRouteWithDomBuilder());
    }
  
    @Test
    public void size1000DomFirstTest() throws Exception {
        simpleTest(1000, new TestRouteWithDomFirstOneOnlyBuilder());
    }
    
    private class TestRouteWithXPathBuilder extends BaseRouteBuilder {
        @Override
        public void configure() {
            from("cxf://" + testAddress + "?dataFormat=PAYLOAD")
                    .streamCaching()
                    .process(new XPathProcessor())
                    .process(new ResponseProcessor());
        }
    }
    
    private class TestRouteWithXPathStringResultBuilder extends BaseRouteBuilder {
        @Override
        public void configure() {
            from("cxf://" + testAddress + "?dataFormat=PAYLOAD")
                    .streamCaching()
                    .process(new XPathStringResultProcessor())
                    .process(new ResponseProcessor());
        }
    }
    
    private class TestRouteWithDomFirstOneOnlyBuilder extends BaseRouteBuilder {
        @Override
        public void configure() {
            from("cxf://" + testAddress + "?dataFormat=PAYLOAD")
                    .streamCaching()
                    .process(new DomFirstOneOnlyProcessor())
                    .process(new ResponseProcessor());
        }
    }  
    
    private class TestRouteWithDomBuilder extends BaseRouteBuilder {
        @Override
        public void configure() {
            from("cxf://" + testAddress + "?dataFormat=PAYLOAD")
                    .streamCaching()
                    .process(new DomProcessor())
                    .process(new ResponseProcessor());
        }
    }      
    
    //implementation simular to xpath() in route: no data loss
    private class XPathStringResultProcessor implements Processor {
        @Override
        public void process(Exchange exchange) throws Exception {
            Object obj =  exchange.getIn().getBody();
            //xpath expression directly results in a: String  
            String content = (String) XPathBuilder.xpath("//xml/text()").stringResult().evaluate(context, obj, Object.class);
            exchange.getOut().setBody(content);
            exchange.getOut().setHeaders(exchange.getIn().getHeaders());
        }        
    }  
    
    //this version leads to data loss
    private class XPathProcessor implements Processor {
        @Override
        public void process(Exchange exchange) throws Exception {
            Object obj =  exchange.getIn().getBody();
            //xpath expression results in a: net.sf.saxon.dom.DOMNodeList
            //after which it is converted to a String
            String content = XPathBuilder.xpath("//xml/text()").evaluate(context, obj, String.class);
            exchange.getOut().setBody(content);
            exchange.getOut().setHeaders(exchange.getIn().getHeaders());
        }        
    }       
    
    //this version leads to data loss
    private class DomFirstOneOnlyProcessor implements Processor {
        @Override
        public void process(Exchange exchange) throws Exception {
            Object obj =  exchange.getIn().getBody();
            @SuppressWarnings("unchecked")
            CxfPayload<SoapHeader> payload = (CxfPayload<SoapHeader>) obj;
            Element el = payload.getBody().get(0);
            Text textnode = (Text) el.getFirstChild();
            exchange.getOut().setBody(textnode.getNodeValue());
            exchange.getOut().setHeaders(exchange.getIn().getHeaders());
        }
    }
    
    private class DomProcessor implements Processor {
        @Override
        public void process(Exchange exchange) throws Exception {
            Object obj =  exchange.getIn().getBody();
            @SuppressWarnings("unchecked")
            CxfPayload<SoapHeader> payload = (CxfPayload<SoapHeader>) obj;
            Element el = payload.getBody().get(0);
            Text textnode = (Text) el.getFirstChild();
            
            StringBuilder b = new StringBuilder();
            b.append(textnode.getNodeValue());
            textnode = (Text) textnode.getNextSibling();
            while (textnode != null) {
                //the textnode appears to have siblings!
                b.append(textnode.getNodeValue());
                textnode = (Text) textnode.getNextSibling();
            }
            
            exchange.getOut().setBody(b.toString());
            exchange.getOut().setHeaders(exchange.getIn().getHeaders());
        }
    }    
    
    private class ResponseProcessor implements Processor {
        @Override
        public void process(Exchange exchange) throws Exception {
            Object obj =  exchange.getIn().getBody();
            String content = (String) obj;
            String msgOut = constructSoapMessage(content);
            exchange.getOut().setBody(msgOut);
            exchange.getOut().setHeaders(exchange.getIn().getHeaders());
            exchange.getOut().setHeader(HEADER_SIZE, "" + content.length());
        }        
    }
    
    private void simpleTest(int repeat, BaseRouteBuilder builder) throws Exception {
        setUseRouteBuilder(false);
        context.addRoutes(builder);
        startCamelContext();
        
        String content = StringUtils.repeat("x", repeat);
        String msgIn = constructSoapMessage(content);
        
        Exchange exchgIn = new DefaultExchange(context);
        exchgIn.setPattern(ExchangePattern.InOut);
        exchgIn.getIn().setBody(msgIn);

        //Execute
        Exchange exchgOut = template.send(builder.getTestAddress(), exchgIn);

        //Verify
        String result = exchgOut.getOut().getBody(String.class);
        assertNotNull("response on http call", result);
        
        //check for data loss in received input (after xpath)
        String headerSize = exchgOut.getOut().getHeader(HEADER_SIZE, String.class);
        assertEquals("" + repeat, headerSize);
        
        assertTrue("dataloss in output occurred", result.length() > repeat);
        
        stopCamelContext();
    }
    
        
    private abstract class BaseRouteBuilder extends RouteBuilder {
        protected final String testAddress = getAvailableUrl("test");

        public String getTestAddress() {
            return testAddress;
        }
    } 

    private String getAvailableUrl(String pathEnd) {
        int availablePort = AvailablePortFinder.getNextAvailable();
        String url = "http://localhost:" + availablePort
                + "/" + getClass().getSimpleName();
        return url + "/" + pathEnd;
    }
    
    private String constructSoapMessage(String content) {
        return
            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
            + "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">"
            + "<soapenv:Body><xml>" + content + "</xml></soapenv:Body>"
            + "</soapenv:Envelope>";
    }
}