从InputStream或字节数组构建一个DataSource

8

我正在编写一个小型文件上传工具,作为大型项目的一部分。最初,我是使用Apache commons File实用程序类从servlet处理此任务。下面是我为该服务编写的快速测试客户端的代码示例:

public static void main(String[] args) {

  JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();

 factory.getInInterceptors().add(new LoggingInInterceptor());
 factory.getOutInterceptors().add(new LoggingOutInterceptor());
 factory.setServiceClass(FileUploadService.class);
 factory.setAddress("http://localhost:8080/FileUploadService/FileUploadService");
 FileUploadService client = (FileUploadService) factory.create();

 FileType file = new FileType();
 file.setName("statemo_1256144312279");
 file.setType("xls");

 DataSource source = new FileDataSource(new File("c:/development/statemo_1256144312279.xls"));
 file.setHandler(new DataHandler(source));
 Boolean ret = client.uploadFile(file);
 System.out.println (ret);
 System.exit(0);

这个代码绝对正常。但当我尝试替换Apache commons utilities时,问题就出现了。在上面的代码中,我使用绝对路径名称从文件创建一个 DataSource。在我的servlet中,我无法获取绝对路径名称,而我发送的文件是空的。

以下是servlet代码:

 @SuppressWarnings("unchecked")
    protected void doPost (final HttpServletRequest request, final HttpServletResponse response) 
        throws ServletException, IOException {

    // form should have enctype="multipart/form-data" as an attribute
 if (!ServletFileUpload.isMultipartContent (request)) {
  LOG.info("Invalid form attribute");
  return;
 }

 //DataInputStream in = new DataInputStream(request.getInputStream());

 final DiskFileItemFactory factory = new DiskFileItemFactory ();
 factory.setSizeThreshold(FILE_THRESHOLD_SIZE);

 final ServletFileUpload sfu = new ServletFileUpload (factory);
 sfu.setSizeMax(MAX_FILE_SIZE);

 final HttpSession session = request.getSession();

 final List<FileItem> files = new ArrayList<FileItem>();

 final List<String> filesToProcess = new ArrayList<String>();

 try {
        final List<FileItem> items = sfu.parseRequest(request);

        for (final FileItem f : items) {
            if (!f.isFormField())
                files.add(f);
        }

        /*for (final FileItem f : files) {
         final String absoluteFileName = UPLOAD_DESTINATION + FilenameUtils.getName(f.getName());

            //f.write(new File (absoluteFileName));
            filesToProcess.add(absoluteFileName);
        }*/

        FileItem f = files.get(0);

        LOG.info("File: " + FilenameUtils.getName(f.getName()));
        LOG.info("FileBaseName: " + FilenameUtils.getBaseName(f.getName()));
        LOG.info("FileExtension: " + FilenameUtils.getExtension(f.getName()));

        FileUploadServiceClient client = new FileUploadServiceClient();

        DataSource source = new FileDataSource(new File(f.getName()));

        FileType file = new FileType();
        file.setHandler(new DataHandler(source));
        file.setName(FilenameUtils.getBaseName(f.getName()));
        file.setType(FilenameUtils.getExtension(f.getName()));

        Boolean ret = client.uploadFile(file);

        LOG.info("File uploaded - " + ret);

        filesToProcess.add(UPLOAD_DESTINATION + FilenameUtils.getName(f.getName()));
        session.setAttribute("filesToProcess", filesToProcess);

  final RequestDispatcher dispatcher = request.getRequestDispatcher("Validate");
        if (null != dispatcher) {
         dispatcher.forward(request, response);
        }
    } catch (FileUploadException e) {
        LOG.info("Exception " + e.getMessage());
        e.printStackTrace();
    } catch (Exception e) {
        LOG.info("Exception " + e.getMessage());
        e.printStackTrace();
    }

我已经花了大部分的早晨时间来处理这个问题,但是一直没有进展。即使我完全放弃Apache Commons文件部分并自己处理请求的解析,我仍然无法正确构建DataSource。

谢谢!

4个回答

5
这其实很简单,我只需将InputStream中的字节复制到DataSource中:
FileItem f = files.get(0);

// there is a problem here where the file being created is empty, since we only have a
// partial path:
DataSource source = new FileDataSource(new File(f.getName()));

// because of the above problem, we are going to copy over the data ourselves:
byte[] sourceBytes = f.get();
OutputStream sourceOS = source.getOutputStream();
sourceOS.write(sourceBytes);

没错。你需要发送文件的内容,而不仅仅是文件的句柄 ;) - BalusC

2
在我们的应用程序中,有一些对象具有InputStream和Name属性。我们使用下面的类来构建带有这些属性的DataSource。
public class InputStreamDataSource implements DataSource {

    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    private final String name;

    public InputStreamDataSource(InputStream inputStream, String name) {
        this.name = name;
        try {
            int nRead;
            byte[] data = new byte[16384];
            while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
              buffer.write(data, 0, nRead);
            }
            inputStream.close();
            buffer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public String getContentType() {            
        return new MimetypesFileTypeMap().getContentType(name);
    }

    @Override
    public InputStream getInputStream() throws IOException {
            return new ByteArrayInputStream(buffer.toByteArray());
    }

    @Override
    public String getName() {
       return name;
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        throw new IOException("Read-only data");
    }

}

1
我认为这仍然会将整个流加载到内存中,或者我错了吗?(其他答案也是如此,或者它们会加载一个文件,而我只有一个InputStream)。 - Albert Hendriks

2
  • 这里是commons-email ByteArrayDataSource的代码。
  • 尝试替换apache commons似乎有些奇怪 - 除非你有一个非常好的理由,否则不要这样做。
  • 在servlet中可以获取绝对路径。您可以调用getServletContext().getRealPath("/"),它将返回应用程序的绝对路径,然后您可以相对于它获取文件。

是的,我不想替换apache commons。我甚至没有考虑从上下文中获取路径并以那种方式获取文件。然而,该项目的另一部分会使这变得困难。 - Casey
明天我会看一下ByteArrayDataSource的代码,可能会在我的解决方案中使用它。谢谢! - Casey

0

这里展示的大多数解决方案需要关闭InpustStream(读入内存)。但是,可以将InputStream包装在DataSource对象中而不关闭InputStream:



  private record PipedDataSource(InputStream in, String contentType, String encoding) 
    implements DataSource, EncodingAware {

    public String getContentType() {
      return contentType;
    }

    public InputStream getInputStream() {
      return in;
    }

    public String getName() {
      return "PipedDataSource";
    }

    public OutputStream getOutputStream() throws IOException {
      throw new IOException("No OutputStream");
    }

    @Override
    public String getEncoding() {
      return encoding;
    }
  }

上面的示例还实现了EncodingAware。这可以防止第三方库(例如java.mail.internet.MimeUtility)在获取数据源编码时关闭InputStream。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接