You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 2 Next »

Uploading Files to and Download Files from a Wicket Webapp

This page will give an example of how to allow the user to upload and download images (with thumbnail images). Uploading and downloading any kind of file could be done this way. The checks for content type would just be different. This example code was created for the wicket pastebin. Feel free to download the pastebin's entire codebase in order to see how all the pieces work together.

A Service for Persisting and Retrieving Images

ImageService is an interface. Persisting and retrieving images is the responsibility of any implementation of the ImageService interface. By using an interface, if we decide to persist this information in a different way than what we've done in the past, we just create a different service implementation and use that instead.

 public interface ImageService
 {
     public byte[] getImage(ImageEntry imageEntry)
        throws IOException;
     
     public boolean isImageAvailable(ImageEntry imageEntry);
     
     public Date getLastModifyTime(ImageEntry imageEntry);
     
     public byte[] getThumbnail(ImageEntry imageEntry)
        throws IOException;
 
     @Transactional
     public void save(ImageEntry imageEntry, InputStream imageStream)
        throws IOException;
 }

We have a special implementation of the ImageService interface called ImageServiceImpl. This implementation stores the image and a scaled thumbnail as files on the file system in a special folder. The name of the files are generated via random UUIDs. This file name along with other information, such as the file name on the client's computer and the content type is saved into a database record. Retrieving an image or its thumbnail is done by looking up the record in the database via its unique ID, retrieving the full path to the file system file, reading data from the file, and returning the data as a byte array. It could also be done by returning an InputStream instead of the byte array.

ImageServiceImpl:

import com.mysticcoders.pastebin.model.ImageEntry;

import com.mysticcoders.pastebin.dao.ImageEntryDAO;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;

import java.util.Date;
import java.util.UUID;

import java.awt.Image;
import java.awt.Graphics2D;
import java.awt.RenderingHints;

import java.awt.geom.AffineTransform;

import java.awt.image.BufferedImage;

import javax.swing.ImageIcon;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**

  • An implementation of ImageService.
    *
  • @author pchapman
    */
    public class ImageServiceImpl implements ImageService
    {
    // CONSTANTS

private static final Log logger = LogFactory.getLog(ImageServiceImpl.class);

// CONSTRUCTORS

/**

  • Creates a new instance.
    */
    public ImageServiceImpl()
    Unknown macro: { super(); }

// MEMBERS

public byte[] getImage(ImageEntry imageEntry)
throws IOException
{
if (isImageAvailable(imageEntry))

Unknown macro: { // Open the file, then read it in. ByteArrayOutputStream outStream = new ByteArrayOutputStream(); InputStream inStream = new FileInputStream(new File(imageEntry.getFileName())); copy(inStream, outStream); inStream.close(); outStream.close(); return outStream.toByteArray(); }

else

Unknown macro: { return createNotAvailableImage(imageEntry.getContentType()); }

}

public Date getLastModifyTime(ImageEntry imageEntry)

Unknown macro: { File f = new File(imageEntry.getFileName()); return new Date(f.lastModified()); }

public boolean isImageAvailable(ImageEntry imageEntry)

Unknown macro: { return ( new File(imageEntry.getFileName()).exists() && new File(imageEntry.getThumbName()).exists() ); }

/* Spring Injected */
private File imageDir;
public void setImageDir(String dirName)

Unknown macro: { imageDir = new File(dirName); }

/* Spring Injected */
private ImageEntryDAO imageEntryDAO;
/** The DAO implementation that will be used internally. */
public void setImageEntryDAO(ImageEntryDAO imageEntryDAO)

Unknown macro: { this.imageEntryDAO = imageEntryDAO; }

public byte[] getThumbnail(ImageEntry imageEntry)
throws IOException
{
if (isImageAvailable(imageEntry))

Unknown macro: { // Open the file, then read it in. ByteArrayOutputStream outStream = new ByteArrayOutputStream(); InputStream inStream = new FileInputStream(new File(imageEntry.getThumbName())); copy(inStream, outStream); inStream.close(); outStream.close(); return outStream.toByteArray(); }

else

Unknown macro: { byte[] imageData = createNotAvailableImage(imageEntry.getContentType()); return scaleImage(imageData, getThumbnailSize()); }

}

/* Spring Injected */
private int thumbnailSize;
public int getThumbnailSize()

Unknown macro: { return this.thumbnailSize; }

public void setThumbnailSize(int size)

Unknown macro: { this.thumbnailSize = size; }

// METHODS

/**

  • Copies data from src into dst.
    */
    private void copy(InputStream source, OutputStream destination)
    throws IOException
    {
    try
    Unknown macro: { // Transfer bytes from source to destination byte[] buf = new byte[1024]; int len; while ((len = source.read(buf)) > 0)
    Unknown macro: { destination.write(buf, 0, len); }
    source.close(); destination.close(); if (logger.isDebugEnabled())
    Unknown macro: { logger.debug("Copying image..."); }
    }
    catch (IOException ioe)
    Unknown macro: { logger.error(ioe); throw ioe; }
    }

private File createImageFile(String suffix)
{
UUID uuid = UUID.randomUUID();
File file = new File(imageDir, uuid.toString() + suffix);
if (logger.isDebugEnabled())

Unknown macro: { logger.debug("File " + file.getAbsolutePath() + " created."); }

return file;
}

private byte[] createNotAvailableImage(String contentType)
throws IOException
{
// Load the "Image Not Available"
// image from jar, then write it out.
StringBuffer name = new StringBuffer("com/mysticcoders/resources/ImageNotAvailable.");
if ("image/jpeg".equalsIgnoreCase(contentType))

Unknown macro: { name.append("jpg"); }

else if ("image/png".equalsIgnoreCase(contentType))

Unknown macro: { name.append("png"); }

else

Unknown macro: { name.append("gif"); }

URL url = getClass().getClassLoader().getResource(
name.toString()
);
InputStream in = url.openStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
copy(in, out);
in.close();
out.close();
return out.toByteArray();
}

/** @see ImageService#save(ImageEntry) */
public void save(ImageEntry imageEntry, InputStream imageStream)
throws IOException
{
// Read in the image data.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
copy(imageStream, baos);
baos.close();
byte[] imageData = baos.toByteArray();
baos = null;

// Get the image's suffix
String suffix = null;
if ("image/gif".equalsIgnoreCase(imageEntry.getContentType()))

Unknown macro: { suffix = ".gif"; }

else if ("image/jpeg".equalsIgnoreCase(imageEntry.getContentType()))

Unknown macro: { suffix = ".jpeg"; }

else if ("image/png".equalsIgnoreCase(imageEntry.getContentType()))

Unknown macro: { suffix = ".png"; }

// Create a unique name for the file in the image directory and
// write the image data into it.
File newFile = createImageFile(suffix);
OutputStream outStream = new FileOutputStream(newFile);
outStream.write(imageData);
outStream.close();
imageEntry.setFileName(newFile.getAbsolutePath());

// Create a thumbnail
newFile = createImageFile(".jpeg");
byte[] thumbnailData = scaleImage(imageData, getThumbnailSize());
outStream = new FileOutputStream(newFile);
outStream.write(thumbnailData);
outStream.close();
imageEntry.setThumbName(newFile.getAbsolutePath());

// Save the image info in the database
imageEntryDAO.save(imageEntry);
}

private byte[] scaleImage(byte[] imageData, int maxSize)
throws IOException
{
if (logger.isDebugEnabled())

Unknown macro: { logger.debug("Scaling image..."); }

// Get the image from a file.
Image inImage = new ImageIcon(imageData).getImage();

// Determine the scale.
double scale = (double) maxSize / (double) inImage.getHeight(null);
if (inImage.getWidth(null) > inImage.getHeight(null))

Unknown macro: { scale = (double) maxSize / (double) inImage.getWidth(null); }

// Determine size of new image.
// One of the dimensions should equal maxSize.
int scaledW = (int) (scale * inImage.getWidth(null));
int scaledH = (int) (scale * inImage.getHeight(null));

// Create an image buffer in which to paint on.
BufferedImage outImage = new BufferedImage(
scaledW, scaledH, BufferedImage.TYPE_INT_RGB
);

// Set the scale.
AffineTransform tx = new AffineTransform();

// If the image is smaller than the desired image size,
// don't bother scaling.
if (scale < 1.0d)

Unknown macro: { tx.scale(scale, scale); }

// Paint image.
Graphics2D g2d = outImage.createGraphics();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
g2d.drawImage(inImage, tx, null);
g2d.dispose();

// JPEG-encode the image
// and write to file.
ByteArrayOutputStream os = new ByteArrayOutputStream();
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(os);
encoder.encode(outImage);
os.close();
if (logger.isDebugEnabled())

Unknown macro: { logger.debug("Scaling done."); }

return os.toByteArray();
}
}

Uploading Images

To create a page on which users can upload files (such as images) do the following:

Create a form (wicket.markup.html.form.Form). Set the form to accept multi-part posts by passing true to the setMultiPart(boolean) method of Form. Add a file upload field (wicket.markup.html.form.upload.FileUploadField). In this example, we'll assume the file upload field is called imageFile.

In the form's onSubmit() method, you can process the uploaded file:

// A dao object that will hold information about the uploaded image.
ImageEntry imageEntry = null;
// The FileUpload object that will be provided by wicket that holds info about
// the file uploaded to the webapp
FileUpload fupload = imageFile.getFileUpload();
if (fupload h1. null)

Unknown macro: { // No image was provided getForm().error("Please upload an image."); return; }

else if (fupload.getSize() 0)

Unknown macro: { getForm().error("The image you attempted to upload is empty."); return; }

else if (! checkContentType(fupload.getContentType()))

Unknown macro: { // checkContentType(String) said that they uploaded a file of the wrong type. getForm().error("Only images of types png, jpg, and gif are allowed."); return; }

else

Unknown macro: { // Create a new dao object and populate it with info about the image imageEntry = new ImageEntry(); imageEntry.setContentType(fupload.getContentType()); imageEntry.setName(fupload.getClientFileName()); }

// Save the data
if (imageEntry != null) {
// Get the PasteService that will allow me to save the dao and the file data.
ImageService imageService = (ImageService) PastebinApplication.getInstance().getBean("imageService");
try

Unknown macro: { imageService.save(imageEntry, fupload.getInputStream()); }

catch (IOException ioe)

Unknown macro: { // It'd be nice to have a logger so that we could log the error. getForm().error("Sorry, there was an error uploading the image."); }

}

All the above code really does is get the content type, file name, and an InputStream of the uploaded file. The content type and file name are used to populate a dao object. The dao object and the InputStream are sent to the service to be persisted in whatever method it chooses.

Now for the service:

Downloading Images

In order for users to be able to download stored images, you must make a subclass of wicket.markup.html.DynamicWebResource that can provide a wicket.markup.html.DynamicWebResource.ResourceState. The ResourceState instance provides wicket with the means to determine the resource's content type, it's size, the last time it was modified, and to access the content itself. Being able to access the last time it was modified helps to ensure that browser caching works as desired. If your web resource is changing dynamically, you will wnat to make sure that the ResourceState is returning updated modified times when queried. For our purposes, the modified time never changes. We are most concerned with parsing the URL parameters and thereby determining which image to return and whether to return the full image or a thumbnail. There are two URL parameters we use. The url parameter imageEntryId provides the ID of the image record in the database. The presence of the parameter "thumbnail" indicates that the image's thumbnail should be returned. The value of the thumbnail parameter is not required or checked.

ImageResource:

import java.util.Locale;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.mysticcoders.pastebin.core.ImageService;
import com.mysticcoders.pastebin.dao.ImageEntryDAO;

import com.mysticcoders.pastebin.model.ImageEntry;
import com.mysticcoders.pastebin.web.PastebinApplication;

import wicket.markup.html.DynamicWebResource;

import wicket.util.time.Time;
import wicket.util.value.ValueMap;

/**

  • A resource that provides images from the holding dir. An image can either
  • be a full-size image that has been uploaded, or a thumbnail.
    *
  • @author pchapman
    */
    public class ImageResource extends DynamicWebResource
    {
    // CONSTANTS

public static final Log logger = LogFactory.getLog(ImageResource.class);

private static ImageService imageService = (ImageService)PastebinApplication.getInstance().getBean("imageService");

private static final long serialVersionUID = 1L;

// CONSTRUCTORS

public ImageResource()

Unknown macro: { super(); }

public ImageResource(Locale local)

Unknown macro: { super(local); }

// METHODS

// MEMBERS

/**

  • Loads the image entry by the Id stored in the parameters, or null if
  • no Id was provided.
  • @return The image entry.
    */
    private ImageEntry getImageEntry(ValueMap params)
    {
    ImageEntry imageEntry = null;
    try {
    if (params.containsKey("imageEntryId"))
    Unknown macro: { ImageEntryDAO imageEntryDAO = (ImageEntryDAO) PastebinApplication.getInstance() .getBean("imageEntryDAO"); imageEntry = (ImageEntry) imageEntryDAO.lookupImageEntry( new Long(params.getLong("imageEntryId")) ); if (logger.isDebugEnabled())
    Unknown macro: { logger.debug("imageEntry.name}
    }
    return imageEntry;
    } catch (Exception e)
    Unknown macro: { logger.error(e); return null; }
    }

@Override
protected ResourceState getResourceState()
{
ValueMap params = getParameters();

ImageEntry imageEntry = getImageEntry(params);
if (imageEntry == null)

Unknown macro: { return new ResourceState(); }

ImageResourceState state =
new ImageResourceState(
Time.valueOf(
imageService.getLastModifyTime(imageEntry)
)
);
if (imageEntry != null) {
try {
if (isThumbnail(params))

Unknown macro: { state.setContentType("image/jpeg"); state.setData(imageService.getThumbnail(imageEntry)); }

else

Unknown macro: { state.setContentType(imageEntry.getContentType()); state.setData(imageService.getImage(imageEntry)); }

} catch (Exception e)

Unknown macro: { logger.error(e); }

}

return state;
}

public boolean isThumbnail(ValueMap params)

Unknown macro: { return params.containsKey("thumbnail"); }

class ImageResourceState extends ResourceState
{
// CONSTRUCTORS

ImageResourceState(Time lastModified)

Unknown macro: { super(); this.lastModified = lastModified; }

// MEMBERS

private String contentType;
@Override
public String getContentType()

Unknown macro: { return contentType; }

void setContentType(String contentType)

Unknown macro: { this.contentType = contentType; }

private byte[] data;
@Override
public byte[] getData()

Unknown macro: { return data; }

void setData(byte[] data)

Unknown macro: { this.data = data; }

@Override
public int getLength()

Unknown macro: { return data.length; }

private Time lastModified;
@Override
public Time lastModifiedTime()

Unknown macro: { return lastModified; }

// METHODS
}
}

Registering and Using the Image Resource

To register the ImageResource with wicket, make the following call from your Application subclass' init() method:

getSharedResources().add("imageResource", new ImageResource());

To get the url for an ImageResource:

ResourceReference imageResource = new ResourceReference("imageResource");
String url=RequestCycle.get().urlFor(imageResource)+"?id="+id;

Enjoy!

  • No labels