Package org.xlightweb

Source Code of org.xlightweb.MultipartFormDataRequest$PartHandlerCaller

/*
*  Copyright (c) xlightweb.org, 2008 - 2009. All rights reserved.
*
*  This library is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public
*  License as published by the Free Software Foundation; either
*  version 2.1 of the License, or (at your option) any later version.
*
*  This library is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*  Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public
*  License along with this library; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
* Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
* The latest copy of this software may be found on http://www.xlightweb.org/
*/
package org.xlightweb;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.Map.Entry;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;


import org.xlightweb.AbstractHttpConnection.IMultimodeExecutor;
import org.xlightweb.MultipartFormDataPart.IPartReadListener;
import org.xsocket.DataConverter;
import org.xsocket.Execution;




/**
* A multipart/form-data request, which supports file uploads. Example:
*
* <pre>
*   MultipartFormDataRequest req = new MultipartFormDataRequest(url);
*   req.addPart("file", file);
*   req.addPart("description", "text/plain", "A unsigned ...");
*  
*   IHttpResponse resp = httpClient.call(req);
*   // ...
* </pre>
*
* see <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
*
* @author grro@xlightweb.org
*/
public class MultipartFormDataRequest extends HttpRequest  {
 
    private static final Logger LOG = Logger.getLogger(MultipartFormDataRequest.class.getName());

    private final String boundary;
    private final Map<String, MultipartFormDataPart> parts = new HashMap<String, MultipartFormDataPart>();
   
    private boolean isModifyable;
    private AtomicBoolean isComplete = new AtomicBoolean(false);
   
   
    private AtomicReference<IPartHandler> handlerRef = new AtomicReference<IPartHandler>(null);
    private AtomicReference<PartHandlerInfo> handlerInfo = new AtomicReference<PartHandlerInfo>(null);
   

    // callback execution support
    private final IMultimodeExecutor executor;

   
   
  /**
   * constructor
   *
   * @param url   the url string
   * @throws MalformedURLException if the url is malformed
   */
  public MultipartFormDataRequest(String url) throws MalformedURLException, IOException {
    this(new HttpRequestHeader("POST", url), UUID.randomUUID().toString(), new NonBlockingBodyDataSource(BodyType.IN_MEMORY, AbstractHeader.DEFAULT_ENCODING));
   
    isModifyable = true;
        super.setContentType("multipart/form-data; boundary=" + boundary);
       
        setContentLength(0);
        getNonBlockingBody().setComplete(true);  
  }
 
 
  /**
   * constructor
   *
   * @param request   the request to wrap
   * @throws IOException if an exception occurs
   */
  MultipartFormDataRequest(IHttpRequest request) throws IOException {
        this(request.getRequestHeader(), parseBoundary(request.getRequestHeader()), request.getNonBlockingBody());
        isModifyable = false;
       
        if (!isMultipartFormDataRequest(request)) {
            throw new IOException("request is not a multipart/form-data request ");
        }
       
        NonBlockingBodyDataSource bodyDataSource = request.getNonBlockingBody();
        MultpartRequestBodyDataHandler dh = new MultpartRequestBodyDataHandler();
        bodyDataSource.setDataHandler(dh);
        dh.onData(bodyDataSource);
    }
 
 
  private MultipartFormDataRequest(IHttpRequestHeader header, String boundary, NonBlockingBodyDataSource body) throws IOException {
      super(header);

      AbstractHttpConnection con = body.getConnection();
      if (con != null) {
          this.executor = body.getConnection().getExecutor();
      } else {
          this.executor = new DefaultMultimodeExecutor();
      }
     
      setBodyDataSource(body);
      this.boundary = boundary;
  }

 
  /**
     * return true, if all parts is received completly
     * 
     * @return true, if all parts is received completly
     */
    boolean isComplete() {
        return isComplete.get();
    }

 
  private static String parseBoundary(IHttpRequestHeader requestHeader) throws IOException {
      String contentType= requestHeader.getContentType();
        String attr = contentType.substring("multipart/form-data".length(), contentType.length()).trim();
        String[] atts = attr.split(";");
       
        for (String att : atts) {
            att = att.trim();
            if (att.toLowerCase().startsWith("boundary=")) {
                return att.substring("boundary=".length(), att.length()).trim();
            }
        }
       
      throw new IOException("request " + requestHeader + " does not declares the boundary");
  }
 
 
  void setPartHandler(IPartHandler handler) throws IOException {
      handlerRef.set(handler);
      handlerInfo.set(HttpUtils.getPartHandlerInfo(handler));
     
      List<MultipartFormDataPart> partsCopy = new ArrayList<MultipartFormDataPart>();

      synchronized (parts) {
          partsCopy.addAll(parts.values());
        }
     
      for (MultipartFormDataPart part : partsCopy) {
            onPart(part);
        }
  }
  
 
 
  void addPart(MultipartFormDataPart part) throws IOException {
      synchronized (parts) {
          parts.put(part.getDispositionParam("name"), part);
        }
     
      onPart(part);
  }
 
 
 
  private void onPart(final MultipartFormDataPart part) throws IOException {
      IPartHandler handler = handlerRef.get();
     
      if (handler != null) {
          if (handlerInfo.get().isHandlerInvokeOnMessageReceived()) {
             
              IBodyCompleteListener cl = new IBodyCompleteListener() {
                   
                    @Execution(Execution.NONTHREADED)
                    public void onComplete() throws IOException {
 
                        if (handlerInfo.get().isHandlerMultithreaded()) {
                            executor.processMultithreaded(new PartHandlerCaller(part));
                        } else {
                            executor.processNonthreaded(new PartHandlerCaller(part));
                        }
                    }
                };
               
                part.getNonBlockingBody().addCompleteListener(cl);
             
          } else {
              if (handlerInfo.get().isHandlerMultithreaded()) {
                  executor.processMultithreaded(new PartHandlerCaller(part));
                } else {
                    executor.processNonthreaded(new PartHandlerCaller(part));
                }
          }
      }
  }
 
 
  private final class PartHandlerCaller implements Runnable {
     
      private final MultipartFormDataPart part;
     
      public PartHandlerCaller(MultipartFormDataPart part) {
          this.part = part;
        }
     
      public void run() {
          IPartHandler handler = handlerRef.get();
          if (handler != null) {
              try {
                  handler.onPart(part);
              } catch (IOException ioe) {
                    part.destroy();
                  throw new RuntimeException(ioe);
              }
          }
      }
  }
 
 
  /**
   * {@inheritDoc}
   */
  @Override
  public void setContentType(String type) {
      LOG.warning("current content type " + getContentType() + " will be overriden by " + type);
      super.setContentType(type);
  }
 
 
  IPart getPart(String name) throws IOException {
      throwExceptionIfnotComplete();
      return parts.get(name);
  }
 
 
  Map<String, IPart> getPartMap() throws IOException {
      throwExceptionIfnotComplete();
     
      HashMap<String, IPart> result = new HashMap<String, IPart>();
      for (Entry<String, MultipartFormDataPart> entry : parts.entrySet()) {
          result.put(entry.getKey(), entry.getValue());
        }
     
      return result;
  }
 
 
  Set<String> getPartnameSet() throws IOException {
      throwExceptionIfnotComplete();
      return Collections.unmodifiableSet(parts.keySet());
  }
 
  private void throwExceptionIfnotComplete() throws IOException {
      if (!isComplete.get()) {
          LOG.warning("request " + getRequestHeader() + " is not received completly (hint: set @InvokeOn(InvokeOn.MESSAGE_RECEIVED) or uses a IBodyCompleteListener)");
            throw new IOException("request is not received completly (hint: set @InvokeOn(InvokeOn.MESSAGE_RECEIVED) or uses a IBodyCompleteListener)");
        }
  }
 
 
  static boolean isMultipartFormDataRequest(IHttpRequest request) {
      if (!request.hasBody()) {
          return false;
      }
         
      String contentType = request.getContentType();
      if ((contentType != null) && (contentType.startsWith("multipart/form-data"))) {
          return true;
      }
     
      return false;
  }
 

 
 
  /**
   * adds a part
   *
   * @param name           the part name
   * @param content        the content
   * @throws IOException   if an exception occurs
   */
  public void addPart(String name, String content) throws IOException {
      addPart(name, content, "text/plain", getNonBlockingBody().getEncoding());
  }
 

  /**
   * adds a part
     *
     * @param name           the part name
   * @param contentType    the content type
     * @param content        the content
     * @throws IOException   if an exception occurs
   */
  public void addPart(String name, String contentType, String content) throws IOException {
        addPart(name, content, contentType, HttpUtils.parseEncoding(contentType, getNonBlockingBody().getEncoding()));
    }
   
 
 
  private void addPart(String name, String content, String contentType, String encoding) throws IOException {

      // create part header
        StringBuilder sb = new StringBuilder();
        sb.append("--" + boundary + "\r\n");
        sb.append("Content-Disposition: form-data; name=\"" + name + "\"\r\n");
        sb.append("Content-Type: " + contentType + "; " + encoding + "\r\n");
        sb.append("\r\n");
       
        byte[] header = sb.toString().getBytes("US-ASCII");
       
       
        // create content
        ByteBuffer buffer = DataConverter.toByteBuffer(content, encoding);

       
        // add part
        addPart(DataConverter.toByteBuffer(header), buffer);
  }
     

 
  /**
   * adds a part
   * 
   * @param name           the part name
   * @param file           the file
   * @throws IOException   if an exception occurs
   */
  public void addPart(String name, File file) throws IOException {

      // create part header
      StringBuilder sb = new StringBuilder();
        sb.append("--" + boundary + "\r\n");
        sb.append("Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + file.getName() + "\"\r\n");
        sb.append("Content-Type: " + getContentTypeByFileExtension(file) + "\r\n");
        sb.append("\r\n");
       
        byte[] header = sb.toString().getBytes("US-ASCII");
       
       
        // create content
        RandomAccessFile raf = new RandomAccessFile(file, "r");
        FileChannel fc = raf.getChannel();

        ByteBuffer buffer = ByteBuffer.allocate((int) fc.size());
        fc.read(buffer);
        fc.close();
        raf.close();
        buffer.flip();
       
        // add part
        addPart(DataConverter.toByteBuffer(header), buffer);
  }
   
 

    private void addPart(ByteBuffer... part) throws IOException {
       
        if (!isModifyable) {
            throw new IOException("modifying a recevied message is not supported");           
        }
   
       
        int size = getNonBlockingBody().available();
        if (size < 0) {
            size = 0;
        }
       
       
        // remove final boundary if exists
        if (size > 0) {
            ByteBuffer[] oldContent = getNonBlockingBody().readByteBufferByLength(size);
           
            size = 0;
            for (int i = 0; i < oldContent.length; i++) {
               
                ByteBuffer buf = null;
               
                if (i == (oldContent.length - 1)) {
                    if (("\r\n--" + boundary + "--\r\n").equals(DataConverter.toString(oldContent[i].duplicate(), "US-ASCII"))) {
                        buf = DataConverter.toByteBuffer("\r\n", "US-ASCII");
                    } else {
                        buf = oldContent[i];
                    }
                   
                } else {
                    buf = oldContent[i];
                }
               
                if (buf != null) {
                    size += buf.remaining();
                    getNonBlockingBody().append(true, buf);
                }
            }
        }
       

        for (ByteBuffer buffer : part) {
            size += buffer.remaining();
        }
        getNonBlockingBody().append(true, part, null);
       

        // append final boundary
        StringBuilder sb = new StringBuilder("\r\n--" + boundary + "--\r\n");
        byte[] bound = sb.toString().getBytes("US-ASCII");
        getNonBlockingBody().append(true, ByteBuffer.wrap(bound));
        size = size + bound.length;
       
        // set new content length
        setContentLength(size);
        getNonBlockingBody().setComplete(true);    
    }
   
     
 
          
     
 

  @Execution(Execution.NONTHREADED)
  private final class MultpartRequestBodyDataHandler implements IBodyDataHandler, IPartReadListener {

      private static final int STATE_PRE_BOUNDARY = 0;
      private static final int STATE_READ_HEADER = 5;
      private static final int STATE_COMPLETE = 10;
     
      private int state = 0;
     
     
      public boolean onData(NonBlockingBodyDataSource bodyDataSource) throws BufferUnderflowException {
         
          try {
                if (isComplete.get()) {
                    return true;
                }
             
              switch (state) {
                    case STATE_PRE_BOUNDARY:
                        String leadingString = getNonBlockingBody().readStringByDelimiter("--" + boundary + "\r");
                        if ((leadingString.trim().length() > 0) && (LOG.isLoggable(Level.FINE))) {
                            LOG.fine("first part has leading chars " + leadingString +  " chars will be ignored");
                        }  
                        state = STATE_READ_HEADER;
                        onData(bodyDataSource);
                        break;
        
                       
                    case STATE_READ_HEADER:
                      String header = null;
                      try {
                            header =  getNonBlockingBody().readStringByDelimiter("\r\n\r\n");
                        } catch (BufferUnderflowException bue) {
                            header = getNonBlockingBody().readStringByDelimiter("\r\r");
                        }

                        if (LOG.isLoggable(Level.FINE)) {
                          LOG.fine("header read: " + header);
                        }
                       
                        String[] headerlines = header.split("\r");
                        for (int i = 0; i < headerlines.length; i++) {
                            if (headerlines[i].startsWith("\n")) {
                                headerlines[i] = headerlines[i].substring(1, headerlines[i].length());
                            }
                            headerlines[i] = headerlines[i].trim();
                        }
                       
                        new MultipartFormDataPart(this, boundary, headerlines, getNonBlockingBody());
                        break;
                       
                    default:
                        break;
                }
             
          } catch (IOException ioe) {
              if (LOG.isLoggable(Level.FINE)) {
                  LOG.fine("error occured by parsing multipart request " + ioe.toString());
              }
          }
         
          return true;
      }  
     
     
     
      public void onPartRead(MultipartFormDataPart part) {
          try {
              state = STATE_PRE_BOUNDARY;
              addPart(part);
              getNonBlockingBody().setDataHandler(this);
              onData(getNonBlockingBody());
          } catch (IOException ioe) {
              onException(ioe);
          }
      }
     
     
      public void onLastPartRead(MultipartFormDataPart part) {
          try {
            state = STATE_COMPLETE;
            isComplete.set(true);
              addPart(part);
          } catch (IOException ioe) {
              onException(ioe);
          }
      }

     
      public void onException(IOException ioe) {
          try {
              getNonBlockingBody().destroy();
          } catch (IOException e) {
              if (LOG.isLoggable(Level.FINE)) {
                  LOG.fine("error occred by destroying non blcoking body " + e.toString());
              }
          }
      }
  }
 
    private static class DefaultMultimodeExecutor implements IMultimodeExecutor {
       
        private static final Executor defaultExecutor = Executors.newCachedThreadPool()
       
        public void processMultithreaded(Runnable task) {
            defaultExecutor.execute(task);
        }
       
        public void processNonthreaded(Runnable task) {
            task.run();
        }
    } 
}
TOP

Related Classes of org.xlightweb.MultipartFormDataRequest$PartHandlerCaller

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.