/*
 * Copyright 2001-2004 The Apache Software Foundation.
 * 
 * Licensed 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.
 */

/**
 * @author Rick Rineholt
 * @author Wouter Cloetens (wouter@mind.be)
 */
package org.apache.axis.attachments;

import org.apache.axis.AxisProperties;
import org.apache.axis.components.logger.LogFactory;
import org.apache.axis.transport.http.HTTPConstants;
import org.apache.axis.utils.Messages;
import org.apache.axis.utils.SessionUtils;
import org.apache.commons.logging.Log;

import java.util.Properties;


/**
 * This class is defines utilities for mime.
 */
public class MimeUtils {

    /** Field log           */
    protected static Log log =
        LogFactory.getLog(MimeUtils.class.getName());

    /**
     * Determine as efficiently as possible the content length for attachments in a mail Multipart.
     * @param mp is the multipart to be serarched.
     * @return the actual length.
     *
     * @throws javax.mail.MessagingException
     * @throws java.io.IOException
     */
    public static long getContentLength(javax.mail.Multipart mp)
            throws javax.mail.MessagingException, java.io.IOException {

        int totalParts = mp.getCount();
        long totalContentLength = 0;

        for (int i = 0; i < totalParts; ++i) {
            javax.mail.internet.MimeBodyPart bp =
                    (javax.mail.internet.MimeBodyPart) mp.getBodyPart(i);

            totalContentLength += getContentLength(bp);
        }

        String ctype = mp.getContentType();
        javax.mail.internet.ContentType ct =
                new javax.mail.internet.ContentType(ctype);
        String boundaryStr =
                ct.getParameter("boundary");
        int boundaryStrLen =
                boundaryStr.length()
                + 4;    // must add two for -- prefix and another two for crlf

        // there is one more boundary than parts
        // each parts data must have crlf after it.
        // last boundary has an additional --crlf
        return totalContentLength + boundaryStrLen * (totalParts + 1)
                + 2 * totalParts + +4;
    }

    /**
     * Determine the length for the individual part.
     * @param bp is the part to be searched.
     * @return the length in bytes.
     */
    protected static long getContentLength(
            javax.mail.internet.MimeBodyPart bp) {

        long headerLength = -1L;
        long dataSize = -1L;

        try {
            headerLength = getHeaderLength(bp);

            javax.activation.DataHandler dh = bp.getDataHandler();
            javax.activation.DataSource ds = dh.getDataSource();

            // Do files our selfs since this is costly to read in. Ask the file system.
            // This is 90% of the use of attachments.
            if (ds instanceof javax.activation.FileDataSource) {
                javax.activation.FileDataSource fdh =
                        (javax.activation.FileDataSource) ds;
                java.io.File df = fdh.getFile();

                if (!df.exists()) {
                    throw new RuntimeException(Messages.getMessage("noFile",
                            df.getAbsolutePath()));
                }

                dataSize = df.length();
            } else {
                dataSize = bp.getSize();

                if (-1 == dataSize) {    // Data size is not known so read it the hard way...
                    dataSize = 0;

                    java.io.InputStream in = ds.getInputStream();
                    byte[] readbuf = new byte[64 * 1024];
                    int bytesread;

                    do {
                        bytesread = in.read(readbuf);

                        if (bytesread > 0) {
                            dataSize += bytesread;
                        }
                    } while (bytesread > -1);

                    in.close();
                }
            }
        } catch (Exception e) {
            log.error(Messages.getMessage("exception00"), e);
        }

        return dataSize + headerLength;
    }

    /**
     * Gets the header length for any part.
     * @param bp the part to determine the header length for.
     * @return the length in bytes.
     *
     * @throws javax.mail.MessagingException
     * @throws java.io.IOException
     */
    private static long getHeaderLength(javax.mail.internet.MimeBodyPart bp)
            throws javax.mail.MessagingException, java.io.IOException {

        javax.mail.internet.MimeBodyPart headersOnly =
                new javax.mail.internet.MimeBodyPart(
                        new javax.mail.internet.InternetHeaders(), new byte[0]);

        for (java.util.Enumeration en = bp.getAllHeaders();
             en.hasMoreElements();) {
            javax.mail.Header header = (javax.mail.Header) en.nextElement();

            headersOnly.addHeader(header.getName(), header.getValue());
        }

        java.io.ByteArrayOutputStream bas =
                new java.io.ByteArrayOutputStream(1024 * 16);

        headersOnly.writeTo(bas);
        bas.close();

        return (long) bas.size();    // This has header length plus the crlf part that seperates the data
    }

    // fixme: filter can be replaced as it is not final - is this intended? If
    //  so, document
    // fixme: the fields in filter are not protected - they can be over-written
    /** Field filter           */
    public static String[] filter = new String[]{"Message-ID", "Mime-Version",
                                                 "Content-Type"};

    /**
     * This routine will the multi part type and write it out to a stream.
     *
     * <p>Note that is does *NOT* pass <code>AxisProperties</code>
     * to <code>javax.mail.Session.getInstance</code>, but instead
     * the System properties.
     * </p>
     * @param os is the output stream to write to.
     * @param mp the multipart that needs to be written to the stream.
     */
    public static void writeToMultiPartStream(
            java.io.OutputStream os, javax.mail.internet.MimeMultipart mp) {

        try {
            Properties props = AxisProperties.getProperties();

            props.setProperty(
                    "mail.smtp.host",
                    "localhost");    // this is a bogus since we will never mail it.

            javax.mail.Session session =
                    javax.mail.Session.getInstance(props, null);
            javax.mail.internet.MimeMessage message =
                    new javax.mail.internet.MimeMessage(session);

            message.setContent(mp);
            message.saveChanges();
            message.writeTo(os, filter);
        } catch (javax.mail.MessagingException e) {
            log.error(Messages.getMessage("javaxMailMessagingException00"), e);
        } catch (java.io.IOException e) {
            log.error(Messages.getMessage("javaIOException00"), e);
        }
    }

    /**
     * This routine will get the content type from a mulit-part mime message.
     *
     * @param mp the MimeMultipart
     * @return the content type
     */
    public static String getContentType(javax.mail.internet.MimeMultipart mp) {
        StringBuffer contentType = new StringBuffer(mp.getContentType());
        // TODO (dims): Commons HttpClient croaks if we don't do this.
        //              Need to get Commons HttpClient fixed.
        for(int i=0;i<contentType.length();){
            char ch = contentType.charAt(i);
            if(ch=='\r'||ch=='\n')
                contentType.deleteCharAt(i);
            else
                i++;
        }
        return contentType.toString();
    }

    /**
     * This routine will create a multipart object from the parts and the SOAP content.
     * @param env should be the text for the main root part.
     * @param parts contain a collection of the message parts.
     *
     * @return a new MimeMultipart object
     *
     * @throws org.apache.axis.AxisFault
     */
    public static javax.mail.internet.MimeMultipart createMP(
            String env,
            java.util.Collection parts,
            int sendType)
            throws org.apache.axis.AxisFault {

        javax.mail.internet.MimeMultipart multipart = null;

        try {
            String rootCID = SessionUtils.generateSessionId();

            if(sendType == Attachments.SEND_TYPE_MTOM) {
                multipart = new javax.mail.internet.MimeMultipart(
                        "related;type=\"application/xop+xml\"; start=\"<" + rootCID + ">\"; start-info=\"text/xml; charset=utf-8\"");
            } else {
                multipart = new javax.mail.internet.MimeMultipart(
                        "related; type=\"text/xml\"; start=\"<" + rootCID + ">\"");
            }

            javax.mail.internet.MimeBodyPart messageBodyPart =
                    new javax.mail.internet.MimeBodyPart();

            messageBodyPart.setText(env, "UTF-8");
            if(sendType == Attachments.SEND_TYPE_MTOM){
                messageBodyPart.setHeader("Content-Type",
                    "application/xop+xml; charset=utf-8; type=\"text/xml; charset=utf-8\"");
            } else {
                messageBodyPart.setHeader("Content-Type",
                        "text/xml; charset=UTF-8");
            }
            messageBodyPart.setHeader("Content-Id", "<" + rootCID + ">");
            messageBodyPart.setHeader(
                    HTTPConstants.HEADER_CONTENT_TRANSFER_ENCODING, "binary");
            multipart.addBodyPart(messageBodyPart);

            for (java.util.Iterator it = parts.iterator(); it.hasNext();) {
                org.apache.axis.Part part =
                        (org.apache.axis.Part) it.next();
                javax.activation.DataHandler dh =
                        org.apache.axis.attachments.AttachmentUtils.getActivationDataHandler(
                                part);
                String contentID = part.getContentId();

                messageBodyPart = new javax.mail.internet.MimeBodyPart();

                messageBodyPart.setDataHandler(dh);

                String contentType = part.getContentType();
                if ((contentType == null)
                        || (contentType.trim().length() == 0)) {
                    contentType = dh.getContentType();
                }
                if ((contentType == null)
                        || (contentType.trim().length() == 0)) {
                    contentType = "application/octet-stream";
                }

                messageBodyPart.setHeader(HTTPConstants.HEADER_CONTENT_TYPE,
                        contentType);
                messageBodyPart.setHeader(HTTPConstants.HEADER_CONTENT_ID,
                        "<" + contentID + ">");
                messageBodyPart.setHeader(
                        HTTPConstants.HEADER_CONTENT_TRANSFER_ENCODING,
                        "binary");    // Safe and fastest for anything other than mail;

                for (java.util.Iterator i =
                        part.getNonMatchingMimeHeaders(new String[]{
                            HTTPConstants.HEADER_CONTENT_TYPE,
                            HTTPConstants.HEADER_CONTENT_ID,
                            HTTPConstants.HEADER_CONTENT_TRANSFER_ENCODING}); i.hasNext();) {
                    javax.xml.soap.MimeHeader header = (javax.xml.soap.MimeHeader) i.next();

                     messageBodyPart.setHeader(header.getName(), header.getValue());
                }

                multipart.addBodyPart(messageBodyPart);
            }
        } catch (javax.mail.MessagingException e) {
            log.error(Messages.getMessage("javaxMailMessagingException00"), e);
        }

        return multipart;
    }
}
