/*
 * 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.
 */
package org.jboss.axis.attachments;

import org.jboss.axis.Part;
import org.jboss.axis.components.image.ImageIOFactory;
import org.jboss.axis.transport.http.HTTPConstants;
import org.jboss.axis.utils.IOUtils;
import org.jboss.axis.utils.Messages;
import org.jboss.axis.utils.SessionUtils;
import org.jboss.logging.Logger;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.internet.MimeMultipart;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPException;
import javax.xml.transform.stream.StreamSource;
import java.awt.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;

/**
 * An attachment part. * *
 */
public class AttachmentPartImpl extends AttachmentPart implements Part
{
   /**
    * Field log
    */
   private static Logger log = Logger.getLogger(AttachmentPartImpl.class.getName());

   /**
    * Field datahandler
    */
   private DataHandler datahandler = null;

   /**
    * Field mimeHeaders
    */
   private MimeHeaders mimeHeaders = new MimeHeaders();

   /**
    * Field contentObject
    */
   private Object contentObject;

   /**
    * Constructor AttachmentPart
    */
   public AttachmentPartImpl()
   {
      setMimeHeader(HTTPConstants.HEADER_CONTENT_ID, SessionUtils.generateSessionId());
   }

   /**
    * Constructor AttachmentPart
    *
    * @param dh
    */
   public AttachmentPartImpl(DataHandler dh)
   {
      setMimeHeader(HTTPConstants.HEADER_CONTENT_ID, SessionUtils.generateSessionId());
      datahandler = dh;

      if (dh != null)
         setMimeHeader(HTTPConstants.HEADER_CONTENT_TYPE, dh.getContentType());
   }

   /**
    * Method getActivationDataHandler
    *
    * @return
    */
   public DataHandler getActivationDataHandler()
   {
      return datahandler;
   }

   /**
    * getContentType
    *
    * @return content type
    */
   public String getContentType()
   {
      return getFirstMimeHeader(HTTPConstants.HEADER_CONTENT_TYPE);
   }

   public void setContentType(String contentType)
   {
      super.setContentType(contentType);
      setMimeHeader(HTTPConstants.HEADER_CONTENT_TYPE, contentType);
   }

   /**
    * Add the specified MIME header, as per JAXM.
    *
    * @param header
    * @param value
    */
   public void addMimeHeader(String header, String value)
   {
      mimeHeaders.addHeader(header, value);
   }

   /**
    * Get the specified MIME header.
    *
    * @param header
    * @return
    */
   public String getFirstMimeHeader(String header)
   {
      String[] values = mimeHeaders.getHeader(header.toLowerCase());
      if ((values != null) && (values.length > 0))
      {
         return values[0];
      }
      return null;
   }

   /**
    * check if this Part's mimeheaders matches the one passed in.
    * TODO: Am not sure about the logic.
    */
   public boolean matches(javax.xml.soap.MimeHeaders headers)
   {
      for (Iterator i = headers.getAllHeaders(); i.hasNext();)
      {
         javax.xml.soap.MimeHeader hdr = (javax.xml.soap.MimeHeader)i.next();
         String values[] = mimeHeaders.getHeader(hdr.getName());
         boolean found = false;
         if (values != null)
         {
            for (int j = 0; j < values.length; j++)
            {
               if (!hdr.getValue().equalsIgnoreCase(values[j]))
               {
                  continue;
               }
               found = true;
               break;
            }
         }
         if (!found)
         {
            return false;
         }
      }
      return true;
   }

   /**
    * Content location.
    */
   public String getContentLocation()
   {
      return getFirstMimeHeader(HTTPConstants.HEADER_CONTENT_LOCATION);
   }

   /**
    * Set content location.
    *
    * @param loc
    */
   public void setContentLocation(String loc)
   {
      setMimeHeader(HTTPConstants.HEADER_CONTENT_LOCATION, loc);
   }

   /**
    * Sets Content-Id of this part.
    * already defined.
    *
    * @param newCid new Content-Id
    */
   public void setContentId(String newCid)
   {
      setMimeHeader(HTTPConstants.HEADER_CONTENT_ID, newCid);
   }

   /**
    * Content ID.
    *
    * @return
    */
   public String getContentId()
   {
      return getFirstMimeHeader(HTTPConstants.HEADER_CONTENT_ID);
   }

   /**
    * Get all headers that match
    *
    * @param match
    * @return
    */
   public java.util.Iterator getMatchingMimeHeaders(final String[] match)
   {
      return mimeHeaders.getMatchingHeaders(match);
   }

   /**
    * Get all headers that do not match
    *
    * @param match
    * @return
    */
   public java.util.Iterator getNonMatchingMimeHeaders(final String[] match)
   {
      return mimeHeaders.getNonMatchingHeaders(match);
   }

   /**
    * Retrieves all the headers for this <CODE>
    * AttachmentPart</CODE> object as an iterator over the <CODE>
    * MimeHeader</CODE> objects.
    *
    * @return an <CODE>Iterator</CODE> object with all of the Mime
    *         headers for this <CODE>AttachmentPart</CODE> object
    */
   public Iterator getAllMimeHeaders()
   {
      return mimeHeaders.getAllHeaders();
   }

   /**
    * Changes the first header entry that matches the given name
    * to the given value, adding a new header if no existing
    * header matches. This method also removes all matching
    * headers but the first.
    * <p/>
    * <P>Note that RFC822 headers can only contain US-ASCII
    * characters.</P>
    *
    * @param name  a <CODE>String</CODE> giving the
    *              name of the header for which to search
    * @param value a <CODE>String</CODE> giving the
    *              value to be set for the header whose name matches the
    *              given name
    * @throws java.lang.IllegalArgumentException
    *          if
    *          there was a problem with the specified mime header name
    *          or value
    */
   public void setMimeHeader(String name, String value)
   {
      mimeHeaders.setHeader(name, value);
   }

   /**
    * Removes all the MIME header entries.
    */
   public void removeAllMimeHeaders()
   {
      mimeHeaders.removeAllHeaders();
   }

   /**
    * Removes all MIME headers that match the given name.
    *
    * @param header - the string name of the MIME
    *               header/s to be removed
    */
   public void removeMimeHeader(String header)
   {
      mimeHeaders.removeHeader(header);
   }

   /**
    * Gets the <CODE>DataHandler</CODE> object for this <CODE>
    * AttachmentPart</CODE> object.
    *
    * @return the <CODE>DataHandler</CODE> object associated with
    *         this <CODE>AttachmentPart</CODE> object
    * @throws SOAPException if there is
    *                       no data in this <CODE>AttachmentPart</CODE> object
    */
   public DataHandler getDataHandler() throws SOAPException
   {
      if (datahandler == null)
      {
         throw new SOAPException(Messages.getMessage("noContent"));
      }
      return datahandler;
   }

   /**
    * Sets the given <CODE>DataHandler</CODE> object as the
    * data handler for this <CODE>AttachmentPart</CODE> object.
    * Typically, on an incoming message, the data handler is
    * automatically set. When a message is being created and
    * populated with content, the <CODE>setDataHandler</CODE>
    * method can be used to get data from various data sources into
    * the message.
    *
    * @param datahandler <CODE>DataHandler</CODE> object to
    *                    be set
    * @throws IllegalArgumentException if
    *                                  there was a problem with the specified <CODE>
    *                                  DataHandler</CODE> object
    */
   public void setDataHandler(DataHandler datahandler)
   {
      if (datahandler == null)
      {
         throw new IllegalArgumentException(Messages.getMessage("illegalArgumentException00"));
      }
      this.datahandler = datahandler;
      setMimeHeader(HTTPConstants.HEADER_CONTENT_TYPE, datahandler.getContentType());
   }

   /**
    * Gets the content of this <CODE>AttachmentPart</CODE> object
    * as a Java object. The type of the returned Java object
    * depends on (1) the <CODE>DataContentHandler</CODE> object
    * that is used to interpret the bytes and (2) the <CODE>
    * Content-Type</CODE> given in the header.
    * <p/>
    * <P>For the MIME content types "text/plain", "text/html" and
    * "text/xml", the <CODE>DataContentHandler</CODE> object does
    * the conversions to and from the Java types corresponding to
    * the MIME types. For other MIME types,the <CODE>
    * DataContentHandler</CODE> object can return an <CODE>
    * InputStream</CODE> object that contains the content data as
    * raw bytes.</P>
    * <p/>
    * <P>A JAXM-compliant implementation must, as a minimum,
    * return a <CODE>String</CODE> object corresponding
    * to any content stream with a <CODE>Content-Type</CODE>
    * value of <CODE>text/plain</CODE> and a <CODE>
    * javax.xml.transform.StreamSource</CODE> object
    * corresponding to a content stream with a <CODE>
    * Content-Type</CODE> value of <CODE>text/xml</CODE>. For
    * those content types that an installed <CODE>
    * DataContentHandler</CODE> object does not understand, the
    * <CODE>DataContentHandler</CODE> object is required to
    * return a <CODE>java.io.InputStream</CODE> object with the
    * raw bytes.</P>
    *
    * @return a Java object with the content of this <CODE>
    *         AttachmentPart</CODE> object
    * @throws SOAPException if there is no content set
    *                       into this <CODE>AttachmentPart</CODE> object or if there
    *                       was a data transformation error
    */
   public Object getContent() throws SOAPException
   {
      if (contentObject != null)
         return contentObject;

      try
      {
         if (datahandler != null)
         {
            DataSource ds = datahandler.getDataSource();
            InputStream is = ds.getInputStream();

            String contentType = ds.getContentType();
            if (contentType.equals("text/plain"))
            {
               byte[] bytes = IOUtils.toByteArray(is);
               contentObject = new String(bytes);
            }
            else if (contentType.equals("text/xml") || contentType.equals("application/xml"))
            {
               contentObject = new StreamSource(is);
            }
            else if (contentType.startsWith("multipart/"))
            {
               MimeMultipart mmp = new MimeMultipart(ds);
               contentObject = mmp;
            }
            else if (contentType.equals("image/gif") || contentType.equals("image/jpeg"))
            {
               contentObject = ImageIOFactory.getImageIO().loadImage(is);
            }
            else
            {
               contentObject = is;
            }
         }
      }
      catch (SOAPException e)
      {
         throw e;
      }
      catch (Exception e)
      {
         throw new SOAPException("Cannot get content", e);
      }

      if (contentObject == null)
         throw new SOAPException("Content is not available");

      return contentObject;
   }

   /**
    * Sets the content of this attachment part to that of the
    * given <CODE>Object</CODE> and sets the value of the <CODE>
    * Content-Type</CODE> header to the given type. The type of the
    * <CODE>Object</CODE> should correspond to the value given for
    * the <CODE>Content-Type</CODE>. This depends on the particular
    * set of <CODE>DataContentHandler</CODE> objects in use.
    *
    * @param object      the Java object that makes up
    *                    the content for this attachment part
    * @param contentType the MIME string that
    *                    specifies the type of the content
    * @throws IllegalArgumentException if
    *                                  the contentType does not match the type of the content
    *                                  object, or if there was no <CODE>
    *                                  DataContentHandler</CODE> object for this content
    *                                  object
    * @see #getContent() getContent()
    */
   public void setContent(Object object, String contentType)
   {
      try
      {
         contentObject = object;

         if (object instanceof String)
         {
            InputStream inputStream = new ByteArrayInputStream(((String)contentObject).getBytes());
            ManagedMemoryDataSource source = new ManagedMemoryDataSource(inputStream, contentType);
            datahandler = new DataHandler(source);
         }
         else if (object instanceof StreamSource)
         {
            InputStream inputStream = ((StreamSource)object).getInputStream();
            ManagedMemoryDataSource source = new ManagedMemoryDataSource(inputStream, contentType);
            datahandler = new DataHandler(source);
         }
         else if (object instanceof Image)
         {
            datahandler = new DataHandler(object, contentType);
         }
         else if (object instanceof MimeMultipart)
         {
            ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
            MimeMultipart mmp = (MimeMultipart)object;
            mmp.writeTo(baos);

            InputStream inputStream = new ByteArrayInputStream(baos.toByteArray());
            ManagedMemoryDataSource source = new ManagedMemoryDataSource(inputStream, contentType);
            datahandler = new DataHandler(source);
         }
         else if (object instanceof InputStream)
         {
            InputStream inputStream = ((InputStream)object);
            ManagedMemoryDataSource source = new ManagedMemoryDataSource(inputStream, contentType);
            datahandler = new DataHandler(source);
         }
         else
         {
            throw new IllegalArgumentException("Cannot set content: " + object);
         }
      }
      catch (IllegalArgumentException e)
      {
         throw e;
      }
      catch (Exception e)
      {
         throw new IllegalArgumentException(e.toString());
      }
   }

   public void dispose()
   {
      contentObject = null;
      datahandler = null;
   }

   /**
    * Clears out the content of this <CODE>
    * AttachmentPart</CODE> object. The MIME header portion is left
    * untouched.
    */
   public void clearContent()
   {
      datahandler = null;
      contentObject = null;
   }

   /**
    * Returns the number of bytes in this <CODE>
    * AttachmentPart</CODE> object.
    *
    * @return the size of this <CODE>AttachmentPart</CODE> object
    *         in bytes or -1 if the size cannot be determined
    * @throws SOAPException if the content of this
    *                       attachment is corrupted of if there was an exception
    *                       while trying to determine the size.
    */
   public int getSize() throws SOAPException
   {
      if (datahandler == null)
      {
         return 0;
      }
      ByteArrayOutputStream bout = new ByteArrayOutputStream();
      try
      {
         datahandler.writeTo(bout);
      }
      catch (IOException ex)
      {
         log.error(Messages.getMessage("javaIOException00"), ex);
         throw new SOAPException(Messages.getMessage("javaIOException01", ex.getMessage()), ex);
      }
      return bout.size();
   }

   /**
    * Gets all the values of the header identified by the given
    * <CODE>String</CODE>.
    *
    * @param name the name of the header; example:
    *             "Content-Type"
    * @return a <CODE>String</CODE> array giving the value for the
    *         specified header
    * @see #setMimeHeader(String, String) setMimeHeader(String, String)
    */
   public String[] getMimeHeader(String name)
   {
      return mimeHeaders.getHeader(name);
   }

   /**
    * Content ID.
    *
    * @return the contentId reference value that should be used directly
    *         as an href in a SOAP element to reference this attachment.
    *         <B>Not part of JAX-RPC, JAX-M, SAAJ, etc. </B>
    */
   public String getContentIdRef()
   {
      return Attachments.CIDprefix + getContentId();
   }
}