/**
 * 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.cxf.binding.soap.saaj;


import java.io.IOException;
import java.io.OutputStream;
import java.net.ConnectException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ResourceBundle;

import javax.xml.soap.AttachmentPart;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.MimeHeader;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import org.w3c.dom.Node;


import org.apache.cxf.attachment.AttachmentImpl;
import org.apache.cxf.attachment.AttachmentUtil;
import org.apache.cxf.binding.soap.Soap11;
import org.apache.cxf.binding.soap.Soap12;
import org.apache.cxf.binding.soap.SoapFault;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.SoapVersion;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.binding.soap.interceptor.SoapOutInterceptor;
import org.apache.cxf.common.i18n.BundleUtils;
import org.apache.cxf.common.i18n.Message;
import org.apache.cxf.common.injection.NoJSR250Annotations;
import org.apache.cxf.helpers.CastUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Attachment;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.staxutils.StaxUtils;
import org.apache.cxf.staxutils.W3CDOMStreamReader;
import org.apache.cxf.staxutils.W3CDOMStreamWriter;



/**
 * Sets up the outgoing chain to build a SAAJ tree instead of writing
 * directly to the output stream. First it will replace the XMLStreamWriter
 * with one which writes to a SOAPMessage. Then it will add an interceptor
 * at the end of the chain in the SEND phase which writes the resulting
 * SOAPMessage.
 */
@NoJSR250Annotations
public class SAAJOutInterceptor extends AbstractSoapInterceptor {
    public static final String ORIGINAL_XML_WRITER 
        = SAAJOutInterceptor.class.getName() + ".original.xml.writer";
    
    private static final ResourceBundle BUNDLE = BundleUtils.getBundle(SAAJOutInterceptor.class);
    
    private MessageFactory factory11;
    private MessageFactory factory12;
    
    public SAAJOutInterceptor() {
        super(Phase.PRE_PROTOCOL);
    }
    public synchronized MessageFactory getFactory(SoapMessage message) throws SOAPException {
        if (message.getVersion() instanceof Soap11) {
            if (factory11 == null) { 
                factory11 = SAAJFactoryResolver.createMessageFactory(message.getVersion());
            } 
            return factory11;
        }
        if (message.getVersion() instanceof Soap12) {
            if (factory12 == null) {
                factory12 = SAAJFactoryResolver.createMessageFactory(message.getVersion());
            }
            return factory12;
        }
        return SAAJFactoryResolver.createMessageFactory(null);
    }
    public void handleMessage(SoapMessage message) throws Fault {
        SOAPMessage saaj = message.getContent(SOAPMessage.class);

        try { 
            if (message.hasHeaders()
                && saaj != null 
                && saaj.getSOAPPart().getEnvelope().getHeader() == null) {

                // creating an empty SOAPHeader at this point in the
                // pre-existing SOAPMessage avoids the <soap:body> and 
                // <soap:header> appearing in reverse order when the envolope
                // is written to the wire
                //
                saaj.getSOAPPart().getEnvelope().addHeader();
            }
        } catch (SOAPException e) {
            throw new SoapFault(new Message("SOAPEXCEPTION", BUNDLE, e.getMessage()), 
                                e,
                                message.getVersion().getSender());
        }    

        if (saaj == null) {
            SoapVersion version = message.getVersion();
            try {
                MessageFactory factory = getFactory(message);
                SOAPMessage soapMessage = factory.createMessage();

                SOAPPart soapPart = soapMessage.getSOAPPart();
                XMLStreamWriter origWriter = (XMLStreamWriter)message.get(ORIGINAL_XML_WRITER);
                if (origWriter == null) {
                    origWriter = message.getContent(XMLStreamWriter.class);
                }
                message.put(ORIGINAL_XML_WRITER, origWriter);
                W3CDOMStreamWriter writer = new SAAJStreamWriter(soapPart);
                // Replace stax writer with DomStreamWriter
                message.setContent(XMLStreamWriter.class, writer);
                message.setContent(SOAPMessage.class, soapMessage);
                message.setContent(Node.class, soapMessage.getSOAPPart());
                
                
            } catch (SOAPException e) {
                throw new SoapFault(new Message("SOAPEXCEPTION", BUNDLE, e.getMessage()), e, version.getSender());
            }
        } else if (!message.containsKey(ORIGINAL_XML_WRITER)) {
            //as the SOAPMessage already has everything in place, we do not need XMLStreamWriter to write
            //anything for us, so we just set XMLStreamWriter's output to a dummy output stream.         
            XMLStreamWriter origWriter = message.getContent(XMLStreamWriter.class);
            message.put(ORIGINAL_XML_WRITER, origWriter);
            
            XMLStreamWriter dummyWriter = StaxUtils.createXMLStreamWriter(new OutputStream() {
                    public void write(int b) throws IOException {
                    }
                    public void write(byte b[], int off, int len) throws IOException {
                    }                        
                });
            message.setContent(XMLStreamWriter.class, dummyWriter);
        }
        
        // Add a final interceptor to write the message
        message.getInterceptorChain().add(SAAJOutEndingInterceptor.INSTANCE);
    }
    @Override
    public void handleFault(SoapMessage message) {
        super.handleFault(message);
        //need to clear these so the fault writing will work correctly
        message.removeContent(SOAPMessage.class);
        message.remove(SoapOutInterceptor.WROTE_ENVELOPE_START);
        XMLStreamWriter writer = (XMLStreamWriter)message.get(ORIGINAL_XML_WRITER);
        if (writer != null) {
            message.setContent(XMLStreamWriter.class, writer);
            message.remove(ORIGINAL_XML_WRITER);
        }
    }

    
    public static class SAAJOutEndingInterceptor extends AbstractSoapInterceptor {
        public static final SAAJOutEndingInterceptor INSTANCE = new SAAJOutEndingInterceptor();
        
        public SAAJOutEndingInterceptor() {
            super(SAAJOutEndingInterceptor.class.getName(), Phase.PRE_PROTOCOL_ENDING);
        }

        public void handleMessage(SoapMessage message) throws Fault {
            SOAPMessage soapMessage = message.getContent(SOAPMessage.class);
 
            if (soapMessage != null) {
                if (soapMessage.countAttachments() > 0) {
                    if (message.getAttachments() == null) {
                        message.setAttachments(new ArrayList<Attachment>(soapMessage
                                .countAttachments()));
                    }
                    Iterator<AttachmentPart> it = CastUtils.cast(soapMessage.getAttachments());
                    while (it.hasNext()) {
                        AttachmentPart part = it.next();
                        String id = AttachmentUtil.cleanContentId(part.getContentId());
                        AttachmentImpl att = new AttachmentImpl(id);
                        try {
                            att.setDataHandler(part.getDataHandler());
                        } catch (SOAPException e) {
                            throw new Fault(e);
                        }
                        Iterator<MimeHeader> it2 = CastUtils.cast(part.getAllMimeHeaders());
                        while (it2.hasNext()) {
                            MimeHeader header = it2.next();
                            att.setHeader(header.getName(), header.getValue());
                        }
                        message.getAttachments().add(att);
                    }
                }
                
                XMLStreamWriter writer = (XMLStreamWriter)message.get(ORIGINAL_XML_WRITER);
                message.remove(ORIGINAL_XML_WRITER);

                try {
                    if (writer != null) {
                        StaxUtils.copy(new W3CDOMStreamReader(soapMessage.getSOAPPart()), writer);
                        writer.flush();
                        message.setContent(XMLStreamWriter.class, writer);
                    }
                } catch (XMLStreamException e) {
                    if (e.getCause() instanceof ConnectException) {
                        throw new SoapFault(e.getCause().getMessage(), e,
                                            message.getVersion().getSender());
                    } else {
                        throw new SoapFault(new Message("SOAPEXCEPTION", BUNDLE, e.getMessage()), e,
                                            message.getVersion().getSender());
                    }
                }
            }
        }

    }
}
