Austin Chau, Jason Cooper
April 2008
, updated
March 2010
for Java
Note: This article is also available for Python .
A picture is worth a thousand words. This is definitely true with today's web applications where images play a big part in reaching the audience. App Engine allows you to store and serve images quickly and easily through its datastore. In this short article, I will walk you through how to store and serve images in App Engine's datastore using code snippets for storing and retrieving movie information.
Storing Images in the Datastore
For starters, we would like to store each movie in the application as an object of the datastore. This is accomplished by defining a simple Java class that models a movie. For this project, we will use Java Data Objects ( JDO ), a standard interface for storing Java data objects in a persistent database. A Java Persistence API ( JPA ) wrapper is also available, or you can use the low-level API .
The
Movie
class below defines all the attributes a
Movie
object would have. It's a standard Java class peppered with JDO annotations to describe how the various members should be persisted. (For additional background on these annotations, see
Using JDO
.) One of the fields,
image
, will contain the actual bytes that make up the movie's thumbnail image. Therefore, we need to declare the image as a
Blob
, which you can think of as a large array of bytes.
Note: Do not confuse this with the Blobstore API which can also be used to store large data objects such as images but does not do so via the datastore. This article uses the datastore exclusively.
package com.google.appengine.demo.domain; import com.google.appengine.api.datastore.Blob; import com.google.appengine.api.datastore.Key; import javax.jdo.annotations.Extension; import javax.jdo.annotations.IdGeneratorStrategy; import javax.jdo.annotations.IdentityType; import javax.jdo.annotations.PersistenceCapable; import javax.jdo.annotations.Persistent; import javax.jdo.annotations.PrimaryKey; /** * JDO-annotated model class for storing movie properties; movie's promotional * image is stored as a Blob (large byte array) in the image field. */ @PersistenceCapable(identityType = IdentityType.APPLICATION) public class Movie { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private String title; @Persistent @Extension(vendorName="datanucleus", key="gae.unindexed", value="true") private String imageType; @Persistent private Blob image; //... public Long getId() { return key.getId(); } public String getTitle() { return title; } public String getImageType() { return imageType; } public byte[] getImage() { if (image == null) { return null; } return image.getBytes(); } public void setTitle(String title) { this.title = title; } public void setImageType(String imageType) { this.imageType = imageType; } public void setImage(byte[] bytes) { this.image = new Blob(bytes); } //... }
Now that we have the model defined, we can start using it to store movie objects. We can get movie data from a remote source and feed it into the application. To do this, we'll use App Engine's
URL Fetch API
which can retrieve data from HTTP and HTTPS URLs. Although App Engine for Java includes a familiar
java.net
wrapper on top of the URL Fetch service, we'll use the
low-level API
below. Just specify or otherwise retrieve a reference to the image URL, then pass it into a
URLFetchService
object's
fetch
method to fetch the binary content of a remote image.
Next, create a new
Movie
object and pass the fetched bytes into
setImage
, which creates a new
Blob
object. We'll also store the MIME type of the image (e.g.
image/jpg
,
image/png
, etc.) which should be returned in the 'Content-Type' header.
makePersistent
is then called to commit the change to the datastore.
package com.google.appengine.demo.web; import com.google.appengine.api.urlfetch.HTTPHeader; import com.google.appengine.api.urlfetch.HTTPResponse; import com.google.appengine.api.urlfetch.URLFetchService; import com.google.appengine.api.urlfetch.URLFetchServiceFactory; import com.google.appengine.demo.domain.Movie; import com.google.appengine.demo.repo.PMF; import java.io.IOException; import java.net.URL; import javax.jdo.PersistenceManager; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * GET requests fetch the image at the URL specified by the url query string * parameter, then persist this image along with the title specified by the * title query string parameter as a new Movie object in App Engine's * datastore. */ public class StoreMovieServlet extends HttpServlet { @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { URLFetchService fetchService = URLFetchServiceFactory.getURLFetchService(); // Fetch the image at the location given by the url query string parameter HTTPResponse fetchResponse = fetchService.fetch(new URL( req.getParameter("url"))); String fetchResponseContentType = null; for (HTTPHeader header : fetchResponse.getHeaders()) { // For each request header, check whether the name equals // "Content-Type"; if so, store the value of this header // in a member variable if (header.getName().equalsIgnoreCase("content-type")) { fetchResponseContentType = header.getValue(); break; } } if (fetchResponseContentType != null) { // Create a new Movie instance Movie movie = new Movie(); movie.setTitle(req.getParameter("title")); movie.setImageType(fetchResponseContentType); // Set the movie's promotional image by passing in the bytes pulled // from the image fetched via the URL Fetch service movie.setImage(fetchResponse.getContent()); //... PersistenceManager pm = PMF.get().getPersistenceManager(); try { // Store the image in App Engine's datastore pm.makePersistent(movie); } finally { pm.close(); } } } }
Retrieving & Displaying Images
Now that the objects are populated in our
Movie
datastore, let's set up a request handler to retrieve them dynamically from the datastore and render the objects back to the browser as images.
First, we create a new servlet,
GetImageServlet
, to define the GET request handler. The first thing this request handler does is retrieve the value of 'title' from the URL parameter. Then it retrieves the
Movie
object that matches the title from the datastore by calling
getMovie
, which is defined a bit later.
Once we have the reference to the
Movie
object, we can access its
image
field. In order for the browser to properly render the image, we set our response's HTTP
Content-Type
header to the value we extracted earlier when originally storing the
Movie
object.
package com.google.appengine.demo.web; import com.google.appengine.demo.domain.Movie; import com.google.appengine.demo.repo.PMF; import java.io.IOException; import java.util.List; import javax.jdo.PersistenceManager; import javax.jdo.Query; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * GET requests return the promotional image associated with the movie with the * title specified by the title query string parameter. */ public class GetImageServlet extends HttpServlet { @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { String title = req.getParameter("title"); Movie movie = getMovie(title); if (movie != null && movie.getImageType() != null && movie.getImage() != null) { // Set the appropriate Content-Type header and write the raw bytes // to the response's output stream resp.setContentType(movie.getImageType()); resp.getOutputStream().write(movie.getImage()); } else { // If no image is found with the given title, redirect the user to // a static image resp.sendRedirect("/static/noimage.jpg"); } } //...
The
getMovie
method uses
JDO's query API
to retrieve a
Movie
object that matches the title.
/** * Queries the datastore for the Movie object with the passed-in title. If * found, returns the Movie object; otherwise, returns null. * * @param title movie title to look up */ private Movie getMovie(String title) { PersistenceManager pm = PMF.get().getPersistenceManager(); // Search for any Movie object with the passed-in title; limit the number // of results returned to 1 since there should be at most one movie with // a given title Query query = pm.newQuery(Movie.class, "title == titleParam"); query.declareParameters("String titleParam"); query.setRange(0, 1); try { List<Movie> results = (List<Movie>) query.execute(title); if (results.iterator().hasNext()) { // If the results list is non-empty, return the first (and only) // result return results.get(0); } } finally { query.closeAll(); pm.close(); } return null; }
To make it easy to access the images of the movies in the application, we can map the
GetImageServlet
class to the URL path
'/image'
. In Java, we do this via the standard
web.xml
deployment descriptor file:
<?xml version="1.0" encoding="utf-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <servlet> <servlet-name>StoreMovie</servlet-name> <servlet-class>com.google.appengine.demo.web.StoreMovieServlet</servlet-class> </servlet> <servlet> <servlet-name>GetImage</servlet-name> <servlet-class>com.google.appengine.demo.web.GetImageServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>StoreMovie</servlet-name> <url-pattern>/addMovie</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>GetImage</servlet-name> <url-pattern>/image</url-pattern> </servlet-mapping> </web-app>
Now when a visitor visits the URL - http://mydomain.com/image?title=matrix, an image for the movie "matrix" is returned to the browser.
Resources
We have just shown you how you can easily serve images from your Google App Engine's datastore. Now it's time for you to get your hands dirty with code! Check out the resources below to get started on building your next great app: