JSF - Rendering Images stored in Database
November 3rd, 2008 Posted in JAVA, JSF, Springby Michael Root
This article shows how to render images from data stored in the database to a JSF view.
This solution was created for the following environment, but will work in other configurations.
- JavaServer Faces (JSF) 1.2
- Richfaces 3.2.2.GA
- SpringFramework 2.5.5
- Spring Webflow 2.0.3
- Hibernate 3.2.6.ga
Instead of storing the image data in the database one could store the images to an external directory. I have used external image directories in several projects but I find scalability and image management is more difficult. This post will describe how to perform image management using the database to store the images.
UserImage - BaseDAOHibernateJPA - ImageManager
First it is necessary implement the code to retrieve the data from the database. In our case we were using JPA with Hibernate so I will identify the UserImage entity class along with the DAO and ImageManager classes to achieve this functionality.
-
@Table(name = "user_image")
-
public class UserImage extends BaseObject {
-
private static final long serialVersionUID = 1410602442670943241L;
-
-
@Id
-
@GeneratedValue(strategy = GenerationType.AUTO)
-
@Column(name = "id")
-
private Long id;
-
-
@Version
-
private int version;
-
-
@Enumerated(EnumType.STRING)
-
@Column(name="image_type", nullable=false)
-
private ImageTypeEnum imageType = ImageTypeEnum.USER;
-
-
@ManyToOne(fetch=FetchType.LAZY)
-
@JoinColumn(name="user", nullable=false)
-
private User user;
-
-
@Column(name="filename")
-
private String filename;
-
-
@Column(name="filesize")
-
private long filesize;
-
-
@Column(name="data", columnDefinition="mediumBlob")
-
private byte[] data;
-
-
@Column(name="content_type")
-
private String contentType;
-
-
@Column(name="created_on", updatable=false, insertable=true)
-
private Date createdOn;
-
-
public UserImage() {
-
}
-
-
// Setters and Getters have been removed
-
}
The UserImage.data field has been mapped to a mediumBlob which is fine for MySQL. For Oracle this would have to be changed to Blob. I have also added a ImageType so that this table can be used to store different types of image data.
The following code segment displays the DAO base class which for our purposes is used to retrieve the UserImage data from the database through its CRUD operation getObject.
-
* This class serves as the Base class DAO JPA support.
-
* Can be used for standard CRUD operations.
-
-
*
-
* @author <a href="mailto:mroot@serensystems.com">Michael Root</a>
-
* @since August 31, 2008
-
*/
-
@Repository("baseDAO")
-
public class BaseDAOHibernateJPA implements DAO {
-
protected final transient Log log = LogFactory.getLog(getClass());
-
-
@PersistenceContext
-
protected EntityManager entityManager;
-
-
/**
-
* @see com.serensys.golf.dao.DAO#saveObject( entity)
-
*/
-
public T saveObject(T entity) throws DataAccessException {
-
return entityManager.merge(entity);
-
}
-
-
/**
-
* @see com.serensys.golf.dao.DAO#getObject(Class, Object)
-
*/
-
public T getObject(Class entityClass, Object primaryKey) throws DataAccessException {
-
T entity = entityManager.find(entityClass, primaryKey);
-
if (entity == null) {
-
throw new DataRetrievalFailureException("Entity could not be found by key=" + primaryKey);
-
}
-
return entity;
-
}
-
-
/**
-
* @see com.serensys.golf.dao.DAO#removeObject(Object)
-
*/
-
public void removeObject(Object entity) throws DataAccessException {
-
entityManager.remove(entity);
-
}
-
-
/**
-
* @return the entityManager
-
*/
-
public EntityManager getEntityManager() {
-
return entityManager;
-
}
-
-
/**
-
* @param entityManager the entityManager to set
-
*/
-
public void setEntityManager(EntityManager entityManager) {
-
this.entityManager = entityManager;
-
}
-
}
The following class is the ImageManager that is called from the ImageDBServlet to retrieve the actual UserImage entity.
-
* Implementation of UserImage interface.
-
-
*
-
* @author <a href="mailto:mroot@serensystems.com">Michael Root</a>
-
* @since October 31, 2008
-
*/
-
@Service("imageManager")
-
public class ImageManagerImpl extends BaseManager implements ImageManager {
-
-
/**
-
* @see com.serensys.golf.service.ImageManager#getUserImage(Long)
-
*/
-
@Transactional(readOnly = true)
-
public UserImage getUserImage(Long id) {
-
return this.baseDAO.getObject(UserImage.class, id);
-
}
-
}
ImageDBServlet
The following servlet renders the image data to the output stream as shown in the class below.
-
* Special image servlet for efficiently resolving and rendering image resources from a database.
-
*
-
* @author Michael Root
-
*/
-
public class ImageDBServlet extends HttpServletBean {
-
private static final long serialVersionUID = -8604559719271081403L;
-
-
private static final String HTTP_CONTENT_LENGTH_HEADER = "Content-Length";
-
-
private static final String HTTP_LAST_MODIFIED_HEADER = "Last-Modified";
-
-
private static final String HTTP_EXPIRES_HEADER = "Expires";
-
-
private static final String HTTP_CACHE_CONTROL_HEADER = "Cache-Control";
-
-
private boolean gzipEnabled = true;
-
-
private static ApplicationContext appContext = null;
-
private ImageManager imageManager;
-
-
private Set compressedMimeTypes = new HashSet();
-
{
-
compressedMimeTypes.add("text/css");
-
compressedMimeTypes.add("text/javascript");
-
}
-
-
/**
-
* Method called by base class to allow subclass to initialize. The applicationContext
-
* imageManager will be retrieved from the applicationContext.
-
*/
-
protected void initServletBean() throws ServletException {
-
appContext = WebApplicationContextUtils.getWebApplicationContext(super.getServletContext());
-
if (appContext != null) {
-
imageManager = (ImageManager)appContext.getBean("imageManager");
-
}
-
}
-
-
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
-
-
String rawResourcePath = request.getPathInfo();
-
Long imageId = null;
-
-
if (logger.isDebugEnabled()) {
-
logger.debug("Attempting to GET resource: " + rawResourcePath);
-
}
-
-
// Get the index from the rawResourcePath
-
int idx = rawResourcePath.lastIndexOf("_");
-
-
// If index > 0 then parse imageId
-
if (idx > -1) {
-
String imageIdStr = rawResourcePath.substring(idx+1);
-
if (logger.isDebugEnabled()) {
-
logger.debug("doGet: imageIdStr: " + imageIdStr);
-
}
-
imageId = Long.parseLong(imageIdStr);
-
if (logger.isDebugEnabled()) {
-
logger.debug("doGet: imageId: " + imageId);
-
}
-
}
-
-
if (imageId == null || imageId.longValue() == 0) {
-
if (logger.isDebugEnabled()) {
-
logger.debug("Resource not found: " + rawResourcePath);
-
}
-
response.sendError(HttpServletResponse.SC_NOT_FOUND);
-
return;
-
}
-
-
try {
-
UserImage userImage = imageManager.getUserImage(imageId);
-
prepareResponse(response, userImage);
-
-
OutputStream out = selectOutputStream(request, response);
-
-
try {
-
InputStream in = new ByteArrayInputStream(userImage.getData());
-
try {
-
byte[] buffer = new byte[1024];
-
while (in.available() > 0) {
-
int len = in.read(buffer);
-
out.write(buffer, 0, len);
-
}
-
} finally {
-
in.close();
-
}
-
} finally {
-
out.close();
-
}
-
} finally {
-
-
}
-
}
-
-
private OutputStream selectOutputStream(HttpServletRequest request, HttpServletResponse response)
-
throws IOException {
-
-
String acceptEncoding = request.getHeader("Accept-Encoding");
-
String mimeType = response.getContentType();
-
-
if (gzipEnabled && StringUtils.hasText(acceptEncoding) && acceptEncoding.indexOf("gzip") > -1
-
&& compressedMimeTypes.contains(mimeType)) {
-
logger.debug("Enabling GZIP compression for the current response.");
-
return new GZIPResponseStream(response);
-
} else {
-
return response.getOutputStream();
-
}
-
}
-
-
private void prepareResponse(HttpServletResponse response, UserImage userImage)
-
throws IOException {
-
long lastModified = -1;
-
-
response.setContentType(userImage.getContentType());
-
response.setHeader(HTTP_CONTENT_LENGTH_HEADER, Long.toString(userImage.getFilesize()));
-
response.setDateHeader(HTTP_LAST_MODIFIED_HEADER, lastModified);
-
configureCaching(response, 31556926);
-
}
-
-
/**
-
* Set HTTP headers to allow caching for the given number of seconds.
-
* @param seconds number of seconds into the future that the response should be cacheable for
-
*/
-
private void configureCaching(HttpServletResponse response, int seconds) {
-
// HTTP 1.0 header
-
response.setDateHeader(HTTP_EXPIRES_HEADER, System.currentTimeMillis() + seconds * 1000L);
-
// HTTP 1.1 header
-
response.setHeader(HTTP_CACHE_CONTROL_HEADER, "max-age=" + seconds);
-
}
-
-
private class GZIPResponseStream extends ServletOutputStream {
-
-
private ByteArrayOutputStream byteStream = null;
-
-
private GZIPOutputStream gzipStream = null;
-
-
private boolean closed = false;
-
-
private HttpServletResponse response = null;
-
-
private ServletOutputStream servletStream = null;
-
-
public GZIPResponseStream(HttpServletResponse response) throws IOException {
-
super();
-
closed = false;
-
this.response = response;
-
this.servletStream = response.getOutputStream();
-
byteStream = new ByteArrayOutputStream();
-
gzipStream = new GZIPOutputStream(byteStream);
-
}
-
-
public void close() throws IOException {
-
if (closed) {
-
throw new IOException("This output stream has already been closed");
-
}
-
gzipStream.finish();
-
-
byte[] bytes = byteStream.toByteArray();
-
-
response.setContentLength(bytes.length);
-
response.addHeader("Content-Encoding", "gzip");
-
servletStream.write(bytes);
-
servletStream.flush();
-
servletStream.close();
-
closed = true;
-
}
-
-
public void flush() throws IOException {
-
if (closed) {
-
throw new IOException("Cannot flush a closed output stream");
-
}
-
gzipStream.flush();
-
}
-
-
public void write(int b) throws IOException {
-
if (closed) {
-
throw new IOException("Cannot write to a closed output stream");
-
}
-
gzipStream.write((byte) b);
-
}
-
-
public void write(byte b[]) throws IOException {
-
write(b, 0, b.length);
-
}
-
-
public void write(byte b[], int off, int len) throws IOException {
-
if (closed) {
-
throw new IOException("Cannot write to a closed output stream");
-
}
-
gzipStream.write(b, off, len);
-
}
-
-
public boolean closed() {
-
return (this.closed);
-
}
-
-
public void reset() {
-
// noop
-
}
-
}
-
-
public void setGzipEnabled(boolean gzipEnabled) {
-
this.gzipEnabled = gzipEnabled;
-
}
-
}
This code has implemented compression on the rendered image. More importantly I have implemented caching which will be discussed when we talk about the usage.
Configuration
In order to get the ImageServlet to work, add the following entries to the Web Deployment Descriptor web.xml:
-
com.serensys.golf.webapp.servlets.ImageDBServlet
-
0
-
-
ImageDB Servlet
-
/imagedb/*
Usage
The following code fragment shows the usage to display an image from the database
I am using the graphicImage from the JSF html library. The user.version and user.userImageId are added to the url of the userImage. The image is cached based on this name (e.g. userImage_1_23). If the userImage data does not change the image will be pulled from the cache. If the userImage data is changed the user.version property will be incremented and the userImage will be re-rendered with a new user.version. I hope this article helps in creating a solution to render images from the database.
2 Trackback(s)
You must be logged in to post a comment.