/*
 * 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.apache.axis.attachments;

import org.apache.axis.Part;
import org.apache.axis.components.image.ImageIOFactory;
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 javax.activation.DataHandler;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.SOAPException;
import javax.xml.transform.stream.StreamSource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
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 Log log = LogFactory.getLog(AttachmentPartImpl.class.getName());

   /**
    * Field datahandler
    * TODO: make private?
    */
   javax.activation.DataHandler datahandler = null;

   /**
    * Field mimeHeaders
    */
   private javax.xml.soap.MimeHeaders mimeHeaders =
           new javax.xml.soap.MimeHeaders();

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

   /**
    * the name of a file used to store the data
    */
   private String attachmentFile;

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

   /**
    * Constructor AttachmentPart
    *
    * @param dh
    */
   public AttachmentPartImpl(javax.activation.DataHandler dh)
   {
      setMimeHeader(HTTPConstants.HEADER_CONTENT_ID,
              SessionUtils.generateSessionId());
      datahandler = dh;
      if (dh != null)
      {
         setMimeHeader(HTTPConstants.HEADER_CONTENT_TYPE, dh.getContentType());
      }
   }

   /**
    * on death, we clean up our file
    *
    * @throws Throwable
    */
   protected void finalize() throws Throwable
   {
      dispose();
   }

   /**
    * Method getActivationDataHandler
    *
    * @return
    */
   public javax.activation.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());
      //now look at the source of the data
      javax.activation.DataSource ds = datahandler.getDataSource();
      if (ds instanceof ManagedMemoryDataSource)
      {
         //and get the filename if appropriate
         extractFilename((ManagedMemoryDataSource)ds);
      }


   }

   /**
    * 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;
      }

      if (datahandler == null)
      {
         throw new SOAPException(Messages.getMessage("noContent"));
      }

      javax.activation.DataSource ds = datahandler.getDataSource();
      InputStream is = null;
      try
      {
         is = ds.getInputStream();
         ;
      }
      catch (IOException io)
      {
         log.error(Messages.getMessage("javaIOException00"), io);
         throw new SOAPException(io);
      }
      if (ds.getContentType().equals("text/plain"))
      {
         try
         {
            byte[] bytes = new byte[is.available()];
            is.read(bytes);
            return new String(bytes);
         }
         catch (IOException io)
         {
            log.error(Messages.getMessage("javaIOException00"), io);
            throw new SOAPException(io);
         }
      }
      else if (ds.getContentType().equals("text/xml"))
      {
         return new StreamSource(is);
      }
      else if (ds.getContentType().equals("text/gif") ||
              ds.getContentType().equals("text/jpeg"))
      {
         try
         {
            return ImageIOFactory.getImageIO().loadImage(is);
         }
         catch (Exception ex)
         {
            log.error(Messages.getMessage("javaIOException00"), ex);
            throw new SOAPException(ex);
         }
      }
      return is;
   }


   /**
    * 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)
   {
      InputStream inputStream = null;

      if (object instanceof String)
      {
         inputStream = new ByteArrayInputStream(((String)object).getBytes());
      }
      else if (object instanceof StreamSource)
      {
         inputStream = ((StreamSource)object).getInputStream();
      }
      else if (object instanceof InputStream)
      {
         inputStream = ((InputStream)object);
      }
      else
      {
         throw new IllegalArgumentException("Cannot content type '" + contentType + "' for: " + object);
      }

      try
      {
         ManagedMemoryDataSource source = new ManagedMemoryDataSource(inputStream, ManagedMemoryDataSource.MAX_MEMORY_DISK_CACHED, contentType, true);
         datahandler = new DataHandler(source);
         contentObject = object;
         return;
      }
      catch (IOException io)
      {
         log.error(Messages.getMessage("javaIOException00"), io);
         throw new IllegalArgumentException(Messages.getMessage("illegalArgumentException00"));
      }
   }

   /**
    * 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();
   }

   /**
    * maybe add file name to the attachment
    *
    * @param source the source of the data
    */

   private void extractFilename(ManagedMemoryDataSource source)
   {
      //check for there being a file
      if (source.getDiskCacheFile() != null)
      {
         String path = source.getDiskCacheFile().getAbsolutePath();
         setAttachmentFile(path);
      }
   }

   /**
    * set the filename of this attachment part
    *
    * @param path
    */
   protected void setAttachmentFile(String path)
   {
      attachmentFile = path;
   }

   /**
    * detach the attachment file from this class, so it is not cleaned up
    */
   public void detachAttachmentFile()
   {
      attachmentFile = null;
   }

   /**
    * get the filename of this attachment
    *
    * @return the filename or null for an uncached file
    */
   public String getAttachmentFile()
   {
      return attachmentFile;
   }

   /**
    * when an attachment part is disposed, any associated files
    * are deleted
    */
   public synchronized void dispose()
   {
      if (attachmentFile != null)
      {
         File f = new File(attachmentFile);
         //no need to check for existence here.
         f.delete();
         //set the filename to null to stop repeated use
         setAttachmentFile(null);
      }
   }
}
