Overview

Presently the Struts code does the following: (1) wrap the request in a multipart request that has to be continually unwrapped prior to use and prior to being initialized anyway; (2) parse the request inside the population of the ActionForm, i.e. in RequestUtils.

This is excellent, since the deferment of anything substantial until RequestUtils allows the Struts Team, should you choose to take this assignment, the opportunity to make Struts v1.3 accessible to people wanting to build upload applications.

As things stand, you have to change Struts to build an upload application with any sophistication. Making Struts more flexible is rather easy. Just use the following three interfaces.

An example of how someone might want to use these interfaces is also given following the interfaces.

If the Struts Team wants to stay with the present idea of providing a multipart request to the Action for backwards compatibility, then this can be achieved by populating both such a request, which is really useless, and a MultipartData class.

Framework Code

MultipartFile

public interface MultipartFile
    extends Serializable {
  public long        getSize();
  public void        setSize(long fileSize);
  public String      getName();
  public void        setName(String fileName);
  public String      getContentType();
  public void        setContentType(String fileContentType);
  public byte[]      getData();
  public InputStream getInputStream();
  public void        reset();
}

MultipartData

public interface MultipartData {
  public Iterator getParameterNames();
  public String   getParameter(String name);
  public String[] getParameterValues(String name);
  public Map      getFiles();
}

MultipartHandler

public interface MultipartHandler {
  public void          handleRequest(Object [] params) throws IOException;
  public ActionMapping getMapping();
  public void          setMapping(ActionMapping mapping);
  public ActionServlet getServlet();
  public void          setServlet(ActionServlet servlet);
}

Framework Code Sample Implementation

UploadMultipartFile

public class UploadMultipartFile
    implements MultipartFile {
  private String   contentType;
  private String   name;
  private long     size;
  private FileItem fi;

  public UploadMultipartFile() {
  }

  public UploadMultipartFile(String name,
                             String contentType,
                             long   size) {
    this.name        = name;
    this.contentType = contentType;
    this.size        = size;
  }

  public UploadMultipartFile(FileItem fi) {
    this.fi = fi;
  }

  public long getSize() {
    return size;
  }

  public void setSize(long size) {
    this.size = size;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getContentType() {
    return contentType;
  }

  public void setContentType(String contentType) {
    this.contentType = contentType;
  }

  public byte[] getData() {
    InputStream is = getInputStream();
    if(is != null) {
      int i = (int)getSize();
      if(i > 0) {
        byte data[] = new byte[i];
        try {
          is.read(data);
          is.close();
        } catch(IOException ioe) { }
        return data;
      } else {
        return null;
      }
    } else {
      return null;
    }
  }

  public void reset() {
    if(fi != null) {
      fi.delete();
    }
  }

  public InputStream getInputStream() {
    if(fi == null) {
      return null;
    }

    try {
      return new BufferedInputStream(fi.getInputStream());
    } catch (IOException ioe) {
      return null;
    }
  }

  public FileItem getFileItem() {
    return fi;
  }
}

UploadMultipartData

public class UploadMultipartData
    implements MultipartData {
  private Map parameterNames;
  private Map files;

  public UploadMultipartData(HttpServletRequest req,
                             List               monitors,
                             String             encoding,
                             int                maxFileSize)
      throws UploadException,
             IOException {

    if(req == null) {
      new UploadException(UploadConstant.INVALID_REQUEST);
    }

    parameterNames = Collections.synchronizedMap(new HashMap(89));
    files          = Collections.synchronizedMap(new HashMap(89));

    MultipartHandler handler = new UploadMultipartHandler();

    Object [] objects = new Object [] { req,                              // 0
                                        encoding,                         // 1
                                        monitors,                         // 2
                                        parameterNames,                   // 3
                                        files,                            // 4
                                        UploadConstant.SYSTEM_TEMPDIR,    // 5
                                        new Integer(maxFileSize) };       // 6
    handler.handleRequest(objects);
  }

  public UploadMultipartData(HttpServletRequest req,
                             List               monitors,
                             int                maxFileSize)
      throws UploadException,
             IOException {
    this(req, monitors, UploadConstant.DEFAULT_ENCODING, maxFileSize);
  }

  public UploadMultipartData(HttpServletRequest req,
                             List               monitors)
      throws UploadException,
             IOException {
    this(req, monitors, UploadConstant.MAX_FILE_SIZE);
  }

  public UploadMultipartData(HttpServletRequest req)
      throws UploadException,
             IOException {
    this(req, null, UploadConstant.MAX_FILE_SIZE);
  }

  public Iterator getParameterNames() {
    return this.parameterNames.keySet().iterator();
  }

  public String getParameter(String name) {
    List parameterValues = (List)parameterNames.get(name);

    if(parameterValues == null || parameterValues.size() == 0) {
      return null;
    }

    return (String)parameterValues.get(parameterValues.size() - 1);
  }

  public String[] getParameterValues(String name) {
    List parameterValues = (List)parameterNames.get(name);
    if(parameterValues == null || parameterValues.size() == 0) {
      return null;
    }
    return (String [])parameterValues.toArray();
  }

  public Map getFiles() {
    return (Map)files;
  }
}

UploadMultipartHandler

public class UploadMultipartHandler
    implements MultipartHandler {
  private ActionMapping mapping;
  private ActionServlet servlet;

  public UploadMultipartHandler() {
  }

  public void handleRequest(Object [] params)
      throws IOException {
    handleRequest((HttpServletRequest)params[0],              // req
                              (String)params[1],              // encoding
                                (List)params[2],              // monitors
                                 (Map)params[3],              // parameterNames
                                 (Map)params[4],              // files
                              (String)params[5],              // repositoryPath
                            ((Integer)params[6]).intValue()); // maxFileSize
  }

  private void handleRequest(HttpServletRequest req,
                             String             encoding,
                             List               monitors,
                             Map                parameterNames,
                             Map                files,
                             String             repositoryPath,
                             int                maxFileSize)
      throws IOException {
    UploadFileItemFactory ufiFactory = new UploadFileItemFactory();

    ufiFactory.setMonitors(monitors);
    ufiFactory.setContentLength(req.getContentLength());

    DiskFileUpload dfu = new DiskFileUpload();

    dfu.setFileItemFactory(ufiFactory);
    dfu.setSizeMax(maxFileSize);
    dfu.setSizeThreshold(UploadConstant.BUFFER_SIZE);
    dfu.setRepositoryPath(repositoryPath);

    if(encoding != null) {
      dfu.setHeaderEncoding(encoding);
    }

    List list = null;

    try {
      list = dfu.parseRequest(req);
    } catch(FileUploadException cfue) {
      throw new IOException(cfue.getMessage());
    }

    Object obj = null;

    for(Iterator iterator = list.iterator(); iterator.hasNext();) {
      FileItem fi        = (FileItem)iterator.next();
      String   fieldName = fi.getFieldName();

      if(fi.isFormField()) {
        String data = null;
        if(encoding != null) {
          data = fi.getString(encoding);
        } else {
          data = fi.getString();
        }

        List names = (List)parameterNames.get(fieldName);

        if(names == null) {
          names =  Collections.synchronizedList(new LinkedList());
          parameterNames.put(fieldName, names);
        }

        names.add(data);
      } else {
        String name        = fi.getName();
        String ext         = name;
        String contentType = null;
        int    flag        = -99;
        if(name != null) {
          MultipartFile mf = new UploadMultipartFile(fi);
          mf.setName(name);
          mf.setContentType(contentType = fi.getContentType());
          mf.setSize(fi.getSize());
          files.put(fieldName, mf);
        }
      }
    }
  }

  public ActionServlet getServlet() {
    return this.servlet;
  }

  public void setServlet(ActionServlet servlet) {
    this.servlet = servlet;
  }

  public ActionMapping getMapping() {
    return this.mapping;
  }

  public void setMapping(ActionMapping mapping) {
    this.mapping = mapping;
  }
}

MultipartUtil

public class MultipartUtil {
  public  static final String MULTIPART_FORM_DATA = "multipart/form-data";
  public  static final String CONTENT_TYPE        = "Content-Type";

  public static boolean isMultipartFormData(HttpServletRequest req) {
    String contentType        = null;
    String headerContentType  = req.getHeader(CONTENT_TYPE);
    String requestContentType = req.getContentType();

    if(headerContentType == null && requestContentType != null) {
      contentType = requestContentType;
    } else if(requestContentType == null && headerContentType != null) {
      contentType = headerContentType;
    } else if(headerContentType != null && requestContentType != null) {
      contentType = headerContentType.length() <= requestContentType.length() ? requestContentType : headerContentType;
    }

    return contentType != null && contentType.toLowerCase().startsWith(MULTIPART_FORM_DATA);
  }
}

Sample Application Code Pieces

UploadOutputStream

public class UploadOutputStream
    extends DeferredFileOutputStream {
  private List  monitors;
  private boolean isFormField;

  public UploadOutputStream(int     threshold,
                            File    outputFile,
                            List    monitors,
                            boolean isFormField) {
    super(threshold, outputFile);
    this.monitors = monitors;
    this.isFormField = isFormField;
  }

  public void write(byte data[], int i, int j)
      throws IOException {
    super.write(data, i, j);
    if((monitors != null) && (! isFormField)) {
      for(int k = 0; k < monitors.size(); k++) {
        Monitor monitor = (Monitor)monitors.get(k);
        monitor.read(j);
      }
    }
  }
}

UploadFileItem

public class UploadFileItem
    implements FileItem {
  private static volatile int number = 0;
  private UploadOutputStream  uos;
  private File                tempDir;
  private List                monitors;
  private String              fieldName;
  private String              contentType;
  private String              fileName;
  private byte []             data;
  private int                 threshold;
  private int                 totalSize;
  private boolean             isFormField;

  public UploadFileItem(String  fieldName,
                        String  contentType,
                        boolean isFormField,
                        String  fileName,
                        List    monitors,
                        File    tempDir,
                        int     threshold,
                        int     totalSize) {
    this.fieldName   = fieldName;
    this.contentType = contentType;
    this.isFormField = isFormField;
    this.fileName    = fileName;
    this.monitors    = monitors;
    this.tempDir     = tempDir;
    this.threshold   = threshold;
    this.totalSize   = totalSize;
  }

  public InputStream getInputStream()
      throws IOException {
    if(!uos.isInMemory()) {
      return new FileInputStream(uos.getFile());
    }

    if(data == null) {
      data = this.uos.getData();
    }

    return new ByteArrayInputStream(data);
  }

  public String getContentType() {
    return contentType;
  }

  public String getName() {
    return fileName;
  }

  public boolean isInMemory() {
    return uos.isInMemory();
  }

  public long getSize() {
    if(data != null) {
      return (long)data.length;
    }

    if(uos.isInMemory()) {
      return (long)uos.getData().length;
    } else {
      return uos.getFile().length();
    }
  }

  public byte[] get() {
    if(uos.isInMemory()) {
      if(data == null) {
        data = uos.getData();
      }

      StdOut.log("log.devel","UploadFileItem uos.isInMemory() data = " + data);
      return data;
    }

    byte fisData[] = new byte[(int)getSize()];
    FileInputStream fis = null;
    try {
      fis = new FileInputStream(uos.getFile());
      fis.read(fisData);
    } catch(IOException ioe) {
      fisData = null;
    } finally {
      if(fis != null) {
        try {
          fis.close();
        } catch(IOException ioe1) { }
      }
    }
    return fisData;
  }

  public String getString(String encoding)
      throws UnsupportedEncodingException {
    return new String(get(), encoding);
  }

  public String getString() {
    return new String(get());
  }

  public void write(File file)
      throws Exception {
    if(isInMemory()) {
      FileOutputStream fos = null;
      try {
        fos = new FileOutputStream(file);
        fos.write(get());
      } finally {
        if(fos != null) {
          fos.close();
        }
      }
    } else {
      File outputFile = uos.getFile();

      if(outputFile != null) {
        if(!outputFile.renameTo(file)) {
          BufferedInputStream  bis = null;
          BufferedOutputStream bos = null;
          try {
            bis = new BufferedInputStream(new FileInputStream(outputFile));
            bos = new BufferedOutputStream(new FileOutputStream(file));
            byte streamData[] = new byte[UploadConstant.STREAM_BUFFER_SIZE];
            for(int i = 0; (i = bis.read(streamData)) != -1;)
              bos.write(streamData, 0, i);
          } finally {
            try {
              bis.close();
            } catch(IOException ioe) { }
            try {
              bos.close();
            } catch(IOException ioe1) { }
          }
        }
      } else {
        throw new IOException(UploadConstant.WRITE_ERROR);
      }
    }
  }

  public void delete() {
    data = null;
    File file = uos.getFile();
    if(file != null && file.exists()) {
      file.delete();
    }
  }

  public String  getFieldName() {
    return fieldName;
  }

  public void setFieldName(String fieldName) {
    this.fieldName = fieldName;
  }

  public boolean isFormField() {
    return isFormField;
  }

  public void setFormField(boolean isFormField) {
    this.isFormField = isFormField;
  }

  protected void finalize() {
    File file = uos.getFile();
    if(file != null && file.exists()) {
      file.delete();
    }
  }

  public OutputStream getOutputStream()
      throws IOException {
    if(uos == null) {
      File file = tempDir;

      if(file == null) {
        file = new File(UploadConstant.SYSTEM_TEMPDIR);
      }

      String fileName = UploadConstant.FILE_PREFIX + (number++) + UploadConstant.TMP_EXT;
      File   outputFile = new File(file, fileName);

      outputFile.deleteOnExit();

      uos = new UploadOutputStream(threshold, outputFile, monitors, isFormField());

      if(monitors != null && !isFormField()) {
        Iterator iter = monitors.iterator();
        while(iter.hasNext()) {
          ((Monitor)iter.next()).init(outputFile, totalSize, getContentType());
        }
      }
    }
    return this.uos;
  }
}

UploadFileItemFactory

public class UploadFileItemFactory
    extends DefaultFileItemFactory {
  private List monitors;
  private int  contentLength;

  public UploadFileItemFactory() {
  }

  public FileItem createItem(String  fieldName,
                             String  contentType,
                             boolean isFormField,
                             String  fileName) {
    return new UploadFileItem(fieldName,
                              contentType,
                              isFormField,
                              fileName,
                              monitors,
                              getRepository(),
                              getSizeThreshold(),
                              contentLength);
  }

  public void setContentLength(int contentLength) {
    this.contentLength = contentLength;
  }

  public int  getContentLength() {
    return contentLength;
  }

  public void setMonitors(List monitors) {
    this.monitors = monitors;
  }

  public List getMonitors() {
    return this.monitors;
  }
}

*I have a full application, but no sense putting it all here if there is no interest.

  • No labels