package org.apache.jetspeed.services.upload;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.jetspeed.capability.CapabilityMap;
import org.apache.jetspeed.capability.CapabilityMapFactory;
import org.apache.jetspeed.om.registry.MediaTypeEntry;
import org.apache.jetspeed.services.Registry;
import org.apache.jetspeed.services.resources.JetspeedResources;
import org.apache.turbine.services.upload.TurbineUpload;
import org.apache.turbine.services.upload.TurbineUploadService;
import org.apache.turbine.util.ParameterParser;
import org.apache.turbine.util.TurbineException;
import org.apache.turbine.util.upload.FileItem;

/**
 * <p>
 * This class is an implementation of {@link 
 * org.apache.turbine.services.upload.UploadService}.
 * 
 * <p>
 * Files will be stored in temporary disk storage on in memory, depending on
 * request size, and will be available from the {@link
 * org.apache.turbine.util.ParameterParser} as {@link
 * org.apache.turbine.util.upload.FileItem}s.
 * 
 * <p>
 * This implementation of {@link 
 * org.apache.turbine.services.upload.UploadService} handles multiple files per
 * single html widget, sent using multipar/mixed encoding type, as specified by
 * RFC 1867. Use {@link
 * org.apache.turbine.util.ParameterParser#getFileItems(String)} to acquire an
 * array of {@link org.apache.turbine.util.upload.FileItem}s associated with
 * given html widget.
 * 
 * @author <a href="mailto:shinsuke@yahoo.co.jp">Shinsuke SUGAYA</a>
 */
public class JetspeedUploadService extends TurbineUploadService {
  /**
   * <p>
   * Processes an <a href="http://rf.cx/rfc1867.html">RFC 1867</a> compliant
   * <code>multipart/form-data</code> stream.
   * 
   * @param req
   *            The servlet request to be parsed.
   * @param params
   *            The ParameterParser instance to insert form fields into.
   * @param path
   *            The location where the files should be stored.
   * @exception TurbineException
   *                If there are problems reading/parsing the request or storing
   *                files.
   */
  @Override
  public void parseRequest(HttpServletRequest req, ParameterParser params,
      String path) throws TurbineException {
    String contentType = req.getHeader(CONTENT_TYPE);
    if (!contentType.startsWith(MULTIPART_FORM_DATA)) {
      throw new TurbineException("the request doesn't contain a "
        + MULTIPART_FORM_DATA
        + " stream");
    }
    int requestSize = req.getContentLength();
    if (requestSize == -1) {
      throw new TurbineException("the request was rejected because "
        + "it's size is unknown");
    }
    if (requestSize > TurbineUpload.getSizeMax()) {
      throw new TurbineException("the request was rejected because "
        + "it's size exceeds allowed range");
    }

    // get encoding info
    String encoding =
      JetspeedResources.getString(
        JetspeedResources.CONTENT_ENCODING_KEY,
        "US-ASCII");
    CapabilityMap cm =
      CapabilityMapFactory.getCapabilityMap(req.getHeader("User-Agent"));
    String mimeCode = cm.getPreferredType().getCode();
    if (mimeCode != null) {
      MediaTypeEntry media =
        (MediaTypeEntry) Registry.getEntry(Registry.MEDIA_TYPE, mimeCode);
      if (media != null && media.getCharacterSet() != null) {
        encoding = media.getCharacterSet();
      }
    }

    try {
      byte[] boundary =
        contentType.substring(contentType.indexOf("boundary=") + 9).getBytes();
      InputStream input = req.getInputStream();

      // change start
      MultipartStream multi = new MultipartStream(input, boundary);
      // change end
      multi.setHeaderEncoding(encoding);
      boolean nextPart = multi.skipPreamble();
      while (nextPart) {
        Map headers = parseHeaders(multi.readHeaders());
        String fieldName = getFieldName(headers);
        if (fieldName != null) {
          String subContentType = getHeader(headers, CONTENT_TYPE);
          if (subContentType != null
            && subContentType.startsWith(MULTIPART_MIXED)) {
            // Multiple files.
            byte[] subBoundary =
              subContentType
                .substring(subContentType.indexOf("boundary=") + 9)
                .getBytes();
            multi.setBoundary(subBoundary);
            boolean nextSubPart = multi.skipPreamble();
            while (nextSubPart) {
              headers = parseHeaders(multi.readHeaders());
              if (getFileName(headers) != null) {
                FileItem item = createItem(path, headers, requestSize);
                OutputStream os = item.getOutputStream();
                try {
                  multi.readBodyData(os);
                } finally {
                  os.close();
                }
                params.append(getFieldName(headers), item);
              } else {
                // Ignore anything but files inside
                // multipart/mixed.
                multi.discardBodyData();
              }
              nextSubPart = multi.readBoundary();
            }
            multi.setBoundary(boundary);
          } else {
            if (getFileName(headers) != null) {
              // A single file.
              FileItem item = createItem(path, headers, requestSize);
              OutputStream os = item.getOutputStream();
              try {
                multi.readBodyData(os);
              } finally {
                os.close();
              }
              params.append(getFieldName(headers), item);
            } else {
              // A form field.
              FileItem item = createItem(path, headers, requestSize);
              OutputStream os = item.getOutputStream();
              try {
                multi.readBodyData(os);
              } finally {
                os.close();
              }
              params.append(getFieldName(headers), new String(item.get()));
            }
          }
        } else {
          // Skip this part.
          multi.discardBodyData();
        }
        nextPart = multi.readBoundary();
      }
    } catch (IOException e) {
      throw new TurbineException("Processing of "
        + MULTIPART_FORM_DATA
        + " request failed", e);
    }

  }

}
