users@jersey.java.net

Re: [Jersey] Problem saving binary data using Jersey multipart

From: Craig McClanahan <Craig.McClanahan_at_Sun.COM>
Date: Fri, 30 Jan 2009 03:20:55 -0800

Geoff Sallee wrote:
> Hello,
>
> I just integrated the jersey-multipart-1.0.1 library into my Jersey
> project in order to more easily handle file uploads. The problem is
> that when I attempt to save the file using the InputStream retrieved
> from multiPart.getBodyParts().get(0) (there is only one part in my
> test form) it only saves part of the file to disk. For example, if I
> attempt to upload a 92KB JPEG image it only gets saved as an 8KB JPEG,
> which only contains the top portion of the image. Here is the code I
> am using:
>
> public Response addUserAsset(
> @PathParam("userName") String userName,
> MultiPart multiPart) {
> String relativePath = null;
> String mimeType = null;
> String fileName = null;
> try {
> InputStream inputStream = null;
> for(BodyPart bodyPart : multiPart.getBodyParts()) {
> mimeType = bodyPart.getHeaders().getFirst("Content-Type");
> if(!StringUtils.isEmpty(mimeType) &&
> mimeType.startsWith("image")) {
> String metaData =
> bodyPart.getHeaders().getFirst("Content-Disposition");
> Matcher m = fileNamePattern.matcher(metaData);
> if(m.matches()) {
> fileName = m.group(1);
> }
> BodyPartEntity bpe = (BodyPartEntity)
> bodyPart.getEntity();
> inputStream = bpe.getInputStream();
> break;
> }
> }
>
> if(inputStream != null) {
> String baseDirPath =
> servletContext.getInitParameter("USER_DIGITAL_ASSET_DIR_PATH");
> DigitalAssetAccess access = new
> DigitalAssetAccessImpl(baseDirPath);
> relativePath = access.store(userName, fileName,
> mimeType, inputStream); //THIS METHOD ACTUALLY STORES THE FILE TO DISK
> }
> else {
> result.setSuccess(false);
> result.addMessage(INVALID_VALUE);
> return ResponseUtil.buildResponse(Status.BAD_REQUEST,
> result);
> }
> }
> catch(IOException ioe) {
> result.setSuccess(false);
> result.addMessage(INTERNAL_SERVER_ERROR);
> return
> ResponseUtil.buildResponse(Status.INTERNAL_SERVER_ERROR, result);
> }
> catch(Exception e) {
> result.setSuccess(false);
> result.addMessage(INTERNAL_SERVER_ERROR);
> return
> ResponseUtil.buildResponse(Status.INTERNAL_SERVER_ERROR, result);
> }
> }
>
> My store method uses the InputStream as follows:
>
> public String store(String userLoginId, String fileName,
> String mimeType, InputStream inputStream) throws IOException {
>
> File parentDir = new File(baseDirPath + "/" + userLoginId);
> if (!parentDir.exists()) {
> parentDir.mkdirs();
> }
>
> String assetFileName = getUniqueName(fileName, mimeType);
>
> File assetFile = new File(parentDir, assetFileName);
> if (assetFile.exists()) {
> throw new IllegalStateException(
> "File already exists with the name:" + assetFileName);
> } else {
> assetFile.createNewFile();
> }
>
> OutputStream outputStream = new BufferedOutputStream(
> new FileOutputStream(assetFile), BUFFER_SIZE); //
> BUFFER_SIZE is 1024 * 1024 * 2 (2MB)
>
> int b;
> while ((b = inputStream.read()) != EOF) { // EOF is -1
> outputStream.write(b);
> }
> inputStream.close();
> outputStream.flush();
> outputStream.close();
>
> return userLoginId + "/" + assetFileName;
> }
>
> I have also tested storing the file using the javax.ImageIO package
> with the same results, so I can't imagine it's due to the way the file
> is being stored. Am I missing something important? I did notice the
> mention of calling multipart.cleanup() but that had no effect in my
> case. Right now it looks like the files that are being stored are
> exactly 8KB so I'm thinking it has something to do with the threshold
> value, but I'm not sure what I can do to fix it. Any help is appreciated.
>
That is definitely a red flag that points to a threshold value issue
(although the default buffer size is 4k, so it's definitely odd). Could
you do me the favor of one experiment to nail this down farther? I'd
like you to try setting the threshold value to something larger than the
size of the file (to ensure that it stays buffered in memory and not to
the local disk file that would otherwise be used). If the app works in
this configuration, then we know for sure that something in the
jersey-multipart buffering logic is loopy. If it still doesn't work,
we'll need to keep digging.

To set this configuration, create a "jersey-multipart-config.properties"
file (in WEB-INF/classes if this is a webapp, or on the classpath
otherwise), with an entry like this:

    bufferThreshold = 128000

This will set the buffer size to ~128k, which should be big enough to
avoid the disk buffering for a 92kb image file.

> Regards,
> Geoff

Craig