/*
 * 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.servicemix.eip.patterns;

import javax.jbi.messaging.ExchangeStatus;
import javax.jbi.messaging.InOnly;
import javax.jbi.messaging.InOut;
import javax.jbi.messaging.MessageExchange;
import javax.jbi.messaging.MessagingException;
import javax.jbi.messaging.NormalizedMessage;
import javax.jbi.messaging.RobustInOnly;
import javax.xml.namespace.QName;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import org.apache.servicemix.common.util.MessageUtil;
import org.apache.servicemix.eip.EIPEndpoint;
import org.apache.servicemix.eip.support.ExchangeTarget;
import org.apache.servicemix.jbi.jaxp.SourceTransformer;

/**
 * Implementation of the 
 * <a href="http://www.enterpriseintegrationpatterns.com/DataEnricher.html">Content-Enricher</a> 
 * Pattern. 
 *  
 * @org.apache.xbean.XBean element="content-enricher"
 */
public class ContentEnricher extends EIPEndpoint {

    /**
     * The address of the target endpoint
     */
    private ExchangeTarget target;

    /**
     * the target to enrich the request
     */
    private ExchangeTarget enricherTarget;

    /**
     * the QName of the resulting root node
     */
    private QName enricherElementName = new QName("enricher");

    /**
     * the QName of the element which contains the 'IN Message'
     * within the response message
     */
    private QName requestElementName = new QName("request");

    /**
     * the QName of the element which contains the message 
     * which was produced by the enricherTarget within the 
     * response message
     */
    private QName resultElementName = new QName("result");

    /**
     * Should message properties be copied ?
     */
    private boolean copyProperties = true;

    /**
     * Should message attachments be copied ?
     */
    private boolean copyAttachments = true;

    /**
     * returns the QName of the resulting root node
     * @return QName of the resulting root node
     */
    public QName getEnricherElementName() {
        return enricherElementName;
    }

    /**
     * Sets the QName of the resulting root node
     * @param enricherElementName QName of the resulting root node
     */
    public void setEnricherElementName(QName enricherElementName) {
        this.enricherElementName = enricherElementName;
    }

    /**
     * Returns the QName of the element which contains the 'IN Message'
     * within the response message
     * 
     * @return QName 
     */
    public QName getRequestElementName() {
        return requestElementName;
    }

    /**
     * Sets the QName of the element which contains the 'IN Message'
     * within the response message
     * 
     * @param requestElementName QName
     */
    public void setRequestElementName(QName requestElementName) {
        this.requestElementName = requestElementName;
    }

    /**
     * Returns the QName of the element which contains the message 
     * which was produced by the enricherTarget within the 
     * response message
     * 
     * @return QName
     */
    public QName getResultElementName() {
        return resultElementName;
    }

    /**
     * Sets the QName of the element which contains the message 
     * which was produced by the enricherTarget within the 
     * response message
     * 
     * @param resultElementName QName
     */
    public void setResultElementName(QName resultElementName) {
        this.resultElementName = resultElementName;
    }

    public boolean isCopyProperties() {
        return copyProperties;
    }

    /**
     * If this is set to <code>true</code>, message properties from the incoming exchange and the enricher exchange will be copied
     * to the outgoing message exchange.  The default value is <code>false</code> (do not copy message properties).
     *
     * @param copyProperties 
     */
    public void setCopyProperties(boolean copyProperties) {
        this.copyProperties = copyProperties;
    }

    public boolean isCopyAttachments() {
        return copyAttachments;
    }

    /**
     * If this is set to <code>true</code>, message attachments from the incoming exchange and the enricher exchange will be copied
     * to the outgoing message exchange.  The default value is <code>false</code> (do not copy message atachments).
     *
     * @param copyAttachments
     */
    public void setCopyAttachments(boolean copyAttachments) {
        this.copyAttachments = copyAttachments;
    }

    protected void processAsync(MessageExchange exchange) throws Exception {
        throw new IllegalStateException();
    }

    protected void processSync(MessageExchange exchange) throws Exception {
        throw new IllegalStateException();
    }

    public void process(MessageExchange exchange) throws Exception {

        if (!(exchange instanceof InOnly) && !(exchange instanceof RobustInOnly)) {
            fail(exchange, new UnsupportedOperationException("Use an InOnly or RobustInOnly MEP"));
        }

        // Skip done exchanges
        if (exchange.getStatus() == ExchangeStatus.DONE) {
            return;
            // Handle error exchanges
        } else if (exchange.getStatus() == ExchangeStatus.ERROR) {
            return;
        }

        InOut enricherTargetME = getExchangeFactory().createInOutExchange();
        enricherTarget.configureTarget(enricherTargetME, getContext());
        
        //ensure that both this class and the enricher target can read the message content
        MessageUtil.enableContentRereadability(exchange.getMessage("in"));
        MessageUtil.transferInToIn(exchange, enricherTargetME);

        sendSync(enricherTargetME);

        if (enricherTargetME.getStatus() == ExchangeStatus.ERROR) {
            fail(exchange, enricherTargetME.getError());
            return;
        }

        Document document = combineToDOMDocument(exchange.getMessage("in"), enricherTargetME.getMessage("out"));

        done(enricherTargetME);

        MessageExchange outExchange = getExchangeFactory().createInOnlyExchange();
        NormalizedMessage out = outExchange.createMessage();
        target.configureTarget(outExchange, getContext());
        out.setContent(new DOMSource(document));

        outExchange.setMessage(out, "in");

        if (copyProperties || copyAttachments) {
            copyPropertiesAndAttachments(exchange.getMessage("in"), outExchange.getMessage("in"));
            copyPropertiesAndAttachments(enricherTargetME.getMessage("out"), outExchange.getMessage("in"));
        }
        
        sendSync(outExchange);
        done(exchange);
    }

    /**
     * Combines two NormalizedMessages to one DOM Document. The
     * element Names are specified via the following properties:
     * enricherElementName, requestElementName, resultElementName
     * 
     * Example:
     *    Content of Message1 :
     *    
     *        <hello/>
     *        
     *    Content of Message 2:
     *    
     *        <message2/>
     * 
     *    Result of this method a DOM Document containing the following:
     *    
     *         <enricher>
     *           <request>
     *             <hello/>
     *           </request>
     *           <result>
     *             <message2/>
     *           </result>
     *         </enricher>
     * 
     */
    private Document combineToDOMDocument(NormalizedMessage requestMessage, NormalizedMessage targetResultMessage)
        throws Exception, ParserConfigurationException {

        Node originalDocumentNode = getDOMNode(requestMessage.getContent());
        Node targetResultNode = getDOMNode(targetResultMessage.getContent());

        Document document = new SourceTransformer().createDocument();
        Element enricherElement = createChildElement(enricherElementName, document);
        Element requestElement = createChildElement(requestElementName, document);

        Node node = document.importNode(originalDocumentNode, true);
        requestElement.appendChild(node);
        enricherElement.appendChild(requestElement);
        document.appendChild(enricherElement);

        Element resultElement = createChildElement(resultElementName, document);

        Node node2 = document.importNode(targetResultNode, true);

        resultElement.appendChild(node2);
        enricherElement.appendChild(resultElement);
        return document;

    }

    private Element createChildElement(QName name, Document document) {
        Element elem;
        if ("".equals(name.getNamespaceURI())) {
            elem = document.createElement(name.getLocalPart());
        } else {
            elem = document.createElementNS(name.getNamespaceURI(), name.getPrefix() + ":" + name.getLocalPart());
        }
        return elem;
    }

    private Node getDOMNode(Source source) throws Exception {
        SourceTransformer sourceTransformer = new SourceTransformer();
        Node node = sourceTransformer.toDOMNode(source);
        if (node.getNodeType() == Node.DOCUMENT_NODE) {
            node = ((Document) node).getDocumentElement();
        }
        return node;
    }

    /**
     * The target where the enriched exchanges are sent.
     *
     * @param target
     */
    public void setTarget(ExchangeTarget target) {
        this.target = target;
    }

    /**
     * The target that will receive a copy of the input message and return
     * an addtitional content.
     * 
     * @param enricherTarget
     */
    public void setEnricherTarget(ExchangeTarget enricherTarget) {
        this.enricherTarget = enricherTarget;
    }


    /**
     * Copies properties and attachments from one message to another
     * depending on the endpoint configuration
     *
     * @param from the message containing the properties and attachments
     * @param to the destination message where the properties and attachments are set
     */
    private void copyPropertiesAndAttachments(NormalizedMessage from, NormalizedMessage to) throws MessagingException {
        if (copyProperties) {
            copyProperties(from, to);
        }
        if (copyAttachments) {
            copyAttachments(from, to);
        }
    }

}
