views:

540

answers:

3

I look around and it seems that zipping all the files together is the way to go. If that the case this is the design I am thinking of doing. Please let me know if there is more efficient way of doing this

  • Client select multiples file of downloading, then click Download
  • servlet receive the requests, then do multiple SELECT (files are saved as blob objects) statement to the database.

I can create BufferedOutputStream and write the blobs to different files, and I guess after I done doing that, I can zip the files up. (is this a good way to zip all the files or is there a better and faster way of achieve this?) After done zipping, then send it to the client (not sure how to do that either, please anyone know how to, please help) Please point out if there is any flaw in my design. I post some questions above, and would really appreciated of anyone can help me answer though. Sample code would be terrific. Thank you very much and have a wonderful new year

+1  A: 

You do not need to write the database blobs to temporary files as you can create the zip file on the fly in your servlet. This includes both the complete zip file and all the entries.

Thorbjørn Ravn Andersen
+2  A: 

You shouldn't need to write the files first. You can create a zip file.

FileOutputStream fos = new FileOutputStream(zipFileName);
zipOutStream = new ZipOutputStream(fos);

Then you can add entries for each of the files you are reading from the database

ZipEntry zipEntry = new ZipEntry("NameOfFileToBeAdded");
zipOutStream.putNextEntry(zipEntry);
zipOutStream.write(byteArrayOfFileEntryData);
zipOutStream.closeEntry();

Of course the writes could be done in a loop so the byteArrayOfFileEntryData won't use up all the server memory.

Once all the zip entries are added, close the zip file

zipOutStream.close();

Once the zip file is created you'll still need to return that to the user.

Edit: One option is to create the zip file output stream wrapping the response output stream. That way the zip file doesn't need to be stored on the server either. I haven't tested this option though.

zipOutStream = new ZipOutputStream(response.getOutputStream())

We do the zip file building on a separate machine which has its own Tomcat server to return the zip file. The user is presented with a "wait" page showing a list of the files they selected which automatically refreshes to show the link to the zip file once all the files are zipped. This way the user can rethink the download once they see the size of the zip file. It also keeps the zip files off the main application server.

G_A
You edited your answer, but still the `byteArrayOfFileEntryData` doesn't sound right. Rather use `inputStreamOfFileEntryData`.
BalusC
I didn't know how the data would come from the database, but the argument to write is either an int or byte[] (or byte[] with start position and length).
G_A
The only (right) way is `ResultSet#getBinaryStream()` which returns an `InputStream`.
BalusC
+4  A: 

Basically you just need to construct a new ZipOutputStream around response.getOutputStream() and then add every InputStream from the DB as new ZipEntry. To improve performance, you can wrap the response.getOutputStream() in a BufferedOutputStream beforehand and the InputStream in a BufferedInputStream and use a byte[] buffer.

Here's a basic example:

package com.example;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ZipFileServlet extends HttpServlet {

    private static final int DEFAULT_BUFFER_SIZE = 10240; // 10KB.
    private YourFileDAO yourFileDAO = YourDAOFactory.getYourFileDAO();

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        String[] fileIds = request.getParameterValues("fileId");
        response.setContentType("application/zip");
        response.setHeader("Content-Disposition", "attachment; filename=\"allfiles.zip\"");
        ZipOutputStream output = null;
        byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];

        try {
            output = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream(), DEFAULT_BUFFER_SIZE));

            for (String fileId : fileIds) {
                YourFileItem item = yourFileDAO.find(fileId);
                if (item == null) continue; // Handle yourself. The fileId may be wrong/spoofed.
                InputStream input = null;

                try {
                    input = new BufferedInputStream(item.getInputStream(), DEFAULT_BUFFER_SIZE);
                    output.putNextEntry(new ZipEntry(item.getName()));
                    for (int length = 0; (length = input.read(buffer)) > 0;) {
                        output.write(buffer, 0, length);
                    }
                    output.closeEntry();
                } finally {
                    if (input != null) try { input.close(); } catch (IOException logOrIgnore) { /**/ }
                }
            }
        } finally {
            if (output != null) try { output.close(); } catch (IOException logOrIgnore) { /**/ }
        }
    }

}

which can be invoked by a form like this:

<form action="zipFile" method="post"> 
    <input type="checkbox" name="fileId" value="1"> foo.exe<br>
    <input type="checkbox" name="fileId" value="2"> bar.pdf<br>
    <input type="checkbox" name="fileId" value="3"> waa.doc<br>
    <input type="checkbox" name="fileId" value="4"> baz.html<br>
    <input type="submit" value="download zip">
</form>

That said, do in no way use ByteArrayInputStream/ByteArrayOutputStream as some here may suggest. Those are namely backed by a raw byte[]. You may risk that that your application breaks when the size of all of the files (from all concurrent users!) together inside bytearrays is larger than the available server memory. You already have a stream from the DB and a stream to the response. Just pipe them through a small byte buffer in a read/write loop. You don't need to get hold of the entire inputstream in a (under the hood) massive byte buffer and then write it away to the outputstream.

BalusC
I am sorry that it took so long for me to reply. I have some business that I have to take care. When I read your code, you have YourFileDAO and YourDAOFactory, I dont seem to have those classes. Are they your classes that you created?
Harry Pham
Those are just fictive DAO (data access objects) classes/methods with self-explaining names. If your data access layer code is well designed, you should already have them. If not, I'd suggest to head here: http://balusc.blogspot.com/2008/07/dao-tutorial-data-layer.html
BalusC
Thank you. I have read your blog. Excellent works.I just want to check if my understanding are accurate1. yourFileItem is a class that contain information about the file, e.g name, fileID...2. item.getInputStream() -- I am kind of confuse on this. Can u help me out on this? I am guessing that we are create an InputStream to retrieve the blob file on database. Please forgive my ignorance!!!
Harry Pham
You can use `ResultSet#getBinaryStream()` for that: http://java.sun.com/javase/6/docs/api/java/sql/ResultSet.html#getBinaryStream%28java.lang.String%29
BalusC
thank you very much. I start writing code now.
Harry Pham
How come YourFileItem has a method call getInputStream(). I thought that method getInputStream() return ResultSet#getBinaryStream(), which is in YourFileDAO class.
Harry Pham
Just define the field/method yourself. You aren't prohibited to write own classes and methods or so :) Also see the UserDAO and UserDTO examples in the DAO tutorial.
BalusC
correct me if I am wrong.YourFileItem item = yourFileDAO.find(fileId), according to my understand this line of code actually do a database query to retrieve information of the file (including ResultSet#getBinaryStream). Is this right? Hope I did not ask too many question. I like the way you code, very object oriented, i want to learn. Rest assure that I already read all your DAO tutorial.
Harry Pham
Yes, just substitute them the same way as in UserDAO/UserDTO examples.
BalusC
I finally got it working. I just got one last question for you. This method here, it is assume that the client has to manually unzip the file, is it not? I kind of have a feeling that the server do not have the permission to unzip the file on the client machine.
Harry Pham
That's true. The responsibility/permission is now up to the client. If you want to unzip the file at client side programmatically, you'll need to run a piece of software at the **client machine**. You could use an applet or jnlp for this which is served by your JSP page.
BalusC
"You could use an applet or jnlp for this which is served by your JSP page." You think u can elaborate that sentence a bit more. I wrote a application applet/servlet and it is just a not pretty at all, when I to handle request/session. Plus applet need to be signed to do anything to the local machine. So if I can avoid using applet, it would be better.
Harry Pham
I also don't really like/recommend it ;) Just leave it to the client.
BalusC
:( that is very unfortunate. Let assume that I want to download multiple files from the database and that the result at the client machine is not a zip file but individual files. Do u happen to know, if java have any framework the will handle it? Just want to test my luck here :)
Harry Pham
You can't go around a piece of software which runs at the client machine. Thus, you'll really consider an Applet or JNLP (also known as Java Web Start). Actionscript (also known as Flash/SWF) might be able to do this as well. Or the client has to download the files one by one itself.
BalusC
Did u happen to create a tutorial about http session for servlet? I like your tutorial a lot
Harry Pham
No, I didn't. Its use is however fairly straightforward. Its Javadoc is also self-explaining. If you're stuck somehow in using it or have a specific question, feel free to ask a new question here. You can also give Marty Hall's Coreservlets.com tutorials a look. They're pretty basic and good: http://courses.coreservlets.com/Course-Materials/csajsp2.html
BalusC