# HessianMessageFormatter.java

    /*
     *  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.synapse.format.hessian;

    import org.apache.axiom.om.OMElement;
    import org.apache.axiom.om.OMNode;
    import org.apache.axiom.om.OMOutputFormat;
    import org.apache.axiom.om.OMText;
    import org.apache.axiom.soap.SOAPFault;
    import org.apache.axis2.AxisFault;
    import org.apache.axis2.Constants;
    import org.apache.axis2.context.MessageContext;
    import org.apache.axis2.transport.MessageFormatter;
    import org.apache.axis2.transport.http.util.URLTemplatingUtil;
    import org.apache.commons.io.IOUtils;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.apache.synapse.util.SynapseBinaryDataSource;

    import javax.activation.DataHandler;

    import java.io.BufferedOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.URL;
    import java.util.Iterator;

    /**
     * Enables a message encoded using the Hessian binary protocol to be written to transport by
     * axis2/synapse and this formats the HessianDataSource to a Hessian message.
     *
     * @see org.apache.axis2.transport.MessageFormatter
     * @see org.apache.synapse.util.SynapseBinaryDataSource
     */
    public class HessianMessageFormatter implements MessageFormatter {

        private static final Log log = LogFactory.getLog(HessianMessageFormatter.class);

        /**
         * Formats the content type to be written in to the transport
         *
         * @param msgCtxt message of which the content type has to be formatted
         * @param format fomat of the expected formatted message
         * @param soapActionString soap action of the message
         *
         * @return contentType formatted content type as a String
         */
        public String getContentType(MessageContext msgCtxt, OMOutputFormat format,
                String soapActionString) {

            String contentType = (String) msgCtxt.getProperty(Constants.Configuration.CONTENT_TYPE);
            if (contentType == null) {
                contentType = HessianConstants.HESSIAN_CONTENT_TYPE;
            }

            String encoding = format.getCharSetEncoding();
            if (encoding != null) {
                contentType += "; charset=" + encoding;
            }

            return contentType;
        }

        /**
         * Extract Hessian bytes from the received SOAP message and write it onto the wire
         *
         * @param msgCtx message from which the Hessian message has to be extracted
         * @param format message format to be written
         * @param out stream to which the message is written
         * @param preserve whether to preserve the indentations
         *
         * @throws AxisFault in case of a failure in writing the message to the provided stream
         */
        public void writeTo(MessageContext msgCtx, OMOutputFormat format, OutputStream out,
                boolean preserve) throws AxisFault {

            if (log.isDebugEnabled()) {
                log.debug("Start writing the Hessian message to OutputStream");
            }

            // Check whether the message to be written is a fault message
            if (msgCtx.getFLOW() == MessageContext.OUT_FAULT_FLOW || msgCtx.getEnvelope().hasFault()) {

                SOAPFault soapFault = msgCtx.getEnvelope().getBody().getFault();
                convertAndWriteHessianFault(soapFault, out);
            } else {

                // no differentiation between normal reply and fault (pass the original message through)
                writeHessianMessage(msgCtx, out);
            }

            if (log.isDebugEnabled()) {
                log.debug("Writing message as a Hessian message is successful");
            }
        }

        /**
         * This method is not supported because of large file handling limitations
         *
         * @param msgCtxt message which contains the Hessian message inside the HessianDataSource
         * @param format message format to be written
         *
         * @return Hessian binary bytes of the message
         *
         * @throws AxisFault for any invocation
         */
        public byte[] getBytes(MessageContext msgCtxt, OMOutputFormat format) throws AxisFault {
            throw new AxisFault("Method not supported. Use the "
                    + "HessianMessageFormatter#writeTo method instead");
        }

        /**
         * {@inheritDoc}
         *
         * Simply returns the soapAction unchanged.
         */
        public String formatSOAPAction(MessageContext messageContext, OMOutputFormat format,
                String soapAction) {

            return soapAction;
        }

        /**
         * {@inheritDoc}
         *
         * @return A templated URL based on the given target URL.
         */
        public URL getTargetAddress(MessageContext messageContext, OMOutputFormat format, URL targetURL)
                throws AxisFault {

            return URLTemplatingUtil.getTemplatedURL(targetURL, messageContext, false);
        }

        /**
         * Writes the Hessian message contained in the message context to the provided output stream.
         *
         * @param msgCtxt the message context containing the Hessian message
         * @param out the provided output stream to which the message shall be written
         *
         * @throws AxisFault if an error occurs writing to the output stream
         */
        private void writeHessianMessage(MessageContext msgCtxt, OutputStream out) throws AxisFault {

            OMElement omElement = msgCtxt.getEnvelope().getBody().getFirstElement();
            SynapseBinaryDataSource synapseBinaryDataSource = extractSynapseBinaryDataSource(omElement);

            if (synapseBinaryDataSource != null) {

                InputStream inputStream = null;
                try {
                    inputStream = synapseBinaryDataSource.getInputStream();
                    IOUtils.copy(inputStream, out);
                } catch (IOException e) {
                    handleException("Couldn't get the bytes from the HessianDataSource", e);
                } finally {
                    if (inputStream != null) {
                        try {
                            inputStream.close();
                        } catch (IOException ignore) {
                            log.warn("Error closing input stream.", ignore);
                        }
                    }
                    if (out != null) {
                        try {
                            out.close();
                        } catch (IOException ignore) {
                            log.warn("Error closing output stream.", ignore);
                        }
                    }
                }

            } else {
                handleException("Unable to find the Hessian content in the payload");
            }
        }

        /**
         * Tries to extract the binary data source containing the Hessian message.
         *
         * @param omElement
         *
         * @return the binary data source containing the Hessian message or null, if the OMElement
         *         does not contain a binary datasource.
         */
        private SynapseBinaryDataSource extractSynapseBinaryDataSource(OMElement omElement) {

            SynapseBinaryDataSource synapseBinaryDataSource = null;
            Iterator it = omElement.getChildren();

            while (it.hasNext() && synapseBinaryDataSource == null) {

                OMNode hessianElement = (OMNode) it.next();
                if (hessianElement instanceof OMText) {

                    OMText tempNode = (OMText) hessianElement;
                    if (tempNode.getDataHandler() != null
                            && ((DataHandler) tempNode.getDataHandler()).getDataSource() instanceof SynapseBinaryDataSource) {

                        synapseBinaryDataSource = (SynapseBinaryDataSource) ((DataHandler) tempNode
                                .getDataHandler()).getDataSource();
                    }
                }
            }

            return synapseBinaryDataSource;
        }

        /**
         * Reads details from the SOAPFault and creates a new Hessian fault using those details and
         * writes it to the output stream.
         *
         * @param soapFault the SOAP fault to convert and write as a Hessian fault
         * @param out the output stream to write the Hessian fault to
         *
         * @throws AxisFault if an error occurs writing the message to the output stream
         */
        private void convertAndWriteHessianFault(SOAPFault soapFault, OutputStream out) throws AxisFault {

            BufferedOutputStream faultOutStream = new BufferedOutputStream(out);

            try {
                String hessianFaultCode = "500";
                String hessianFaultMessage = "";
                String hessianFaultDetail = "";

                if (soapFault.getCode() != null) {
                    hessianFaultCode = soapFault.getCode().getText();
                }

                if (soapFault.getReason() != null) {
                    hessianFaultMessage = soapFault.getReason().getText();
                }

                if (soapFault.getDetail() != null) {
                    hessianFaultDetail = soapFault.getDetail().getText();
                }

                HessianUtils.writeFault(hessianFaultCode, hessianFaultMessage, hessianFaultDetail,
                        faultOutStream);
                faultOutStream.flush();

            } catch (IOException e) {
                handleException("Unalbe to write the fault as a Hessian message", e);
            } finally {
                try {
                    if (faultOutStream != null) {
                        faultOutStream.close();
                    }
                } catch (IOException ignore) {
                    log.warn("Error closing output stream.", ignore);
                }
            }
        }

        /**
         * Logs the original exception, wrappes it in an AxisFault and rethrows it.
         *
         * @param msg the error message
         * @param e the original exception
         *
         * @throws AxisFault
         */
        private void handleException(String msg, Exception e) throws AxisFault {
            log.error(msg, e);
            throw new AxisFault(msg, e);
        }

        /**
         * Logs an error message and throws a newly created AxisFault.
         *
         * @param msg the error message
         *
         * @throws AxisFault
         */
        private void handleException(String msg) throws AxisFault {
            log.error(msg);
            throw new AxisFault(msg);
        }
    }