Using MongoDB/GridFS to deliver files

MongoDB has a very nice feature called GridFS, which removes the need to store binary data in the filesystem. This example will feature a small (and completely unstyled) image gallery. The gallery allows you to upload a file and store it into MongoDB.

You can find the source code of this example in the chapter3/mongodb-image directory.

Getting ready

You should have installed the Morphia module in your application and should have a configured up-and-running MongoDB instance.

How to do it...

The application.conf file should feature a complete MongoDB configuration as for any Morphia-enabled application. Furthermore, a special parameter has been introduced, which represents the collection to store the binary data. The parameter is optional anyway. The uploads collection resembles the default.

morphia.db.host=localhost
morphia.db.port=27017
morphia.db.name=images
morphia.db.collection.upload=uploads

The routes file features four routes. One shows the index page, one returns a JSON representation of all images to the client, one gets the image from the database and renders it, and one allows the user to upload the image and store it into the database.

GET     /                 Application.index
GET/images.json           Application.getImages
GET/image/{id}            Application.showImage
POST   /image/            Application.storeImage

The controller implements the routes:

public class Application extends Controller {

    public static void index() {
        render();
    }

    public static void getImages() {
        List<GridFSDBFile> files = GridFsHelper.getFiles();
        Map map = new HashMap();
        map.put("items", files);
        renderJSON(map, new GridFSSerializer());
    }

    public static void storeImage(File image, String desc) {
        notFoundIfNull(image);
        try {
          GridFsHelper.storeFile(desc, image);
        } catch (IOException e) {
          flash("uploadError", e.getMessage());
        }
        index();
    }

    public static void showImage(String id) {
        GridFSDBFile file = GridFsHelper.getFile(id);
        notFoundIfNull(file);
        renderBinary(file.getInputStream(), file.getFilename(), 
            file.getLength(), file.getContentType(), true);
    }
}

As written in the preceding code snippet, an own Serializer for the GridFSDBFile class uses its own Serializer when rendering the JSON reply:

public class GridFSSerializer implements JsonSerializer<GridFSDBFile> 
{

    @Override
    public JsonElement serialize(GridFSDBFile file, Type type,
          JsonSerializationContextctx) {
        String url = createUrlForFile(file);
        JsonObjectobj = new JsonObject();
        obj.addProperty("thumb", url);
        obj.addProperty("large", url);
        obj.addProperty("title", (String)file.get("title"));
        obj.addProperty("link", url);

        return obj;
    }

    private String createUrlForFile(GridFSDBFile file) {
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("id", file.getId().toString());
        return Router.getFullUrl("Application.showImage", map);
    }
}

The GridFSHelper is used to store and read images as binary data from MongoDB:

public class GridFsHelper {

    public static GridFSDBFilegetFile(String id) {
        GridFSDBFile file = getGridFS().findOne(new ObjectId(id));
        return file;
    }

    public static List<GridFSDBFile>getFiles() {
        return getGridFS().find(new BasicDBObject());
    }

    public static void storeFile(String title, File image) throws IOException {
        GridFSfs = getGridFS();
        fs.remove(image.getName()); // delete the old file
        GridFSInputFile gridFile = fs.createFile(image);
        gridFile.save();
        gridFile.setContentType("image/" + FilenameUtils.getExtension(image.getName()));
        gridFile.setFilename(image.getName());
        gridFile.put("title", title);
        gridFile.save();
    }

    private static GridFS getGridFS() {
        String collection = Play.configuration.getProperty("morphia.db.collection.upload", "uploads");
        GridFSfs = new GridFS(MorphiaPlugin.ds().getDB(), collection);
        return fs;
    }
}

As the Dojo Toolkit is used in this example, the main template file needs to be changed to include a class attribute in the body tag. The Dojo Toolkit is a versatile JavaScript library, which features a nice gallery widget used in this example. So the file app/views/main.html needs to be changed:

<!DOCTYPE html>
<html>
    <head>
        <title>#{get 'title' /}</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        #{get 'moreStyles' /}
        <link rel="shortcut icon" type="image/png"href="@{'/public/images/favicon.png'}">
        #{get 'moreScripts' /}
    </head>
    <body class="tundra">
        #{doLayout /}
    </body>
</html>

Furthermore, the index templates file itself needs to be created at app/views/Application/index.html:

#{extends 'main.html' /}
#{set title:'Gallery' /}
#{set 'moreStyles'}
<style type="text/css">
  @import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox/image/resources/image.css";
  @import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit/themes/tundra/tundra.css";
</style>
#{/set}
#{set 'moreScripts'}
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js"djConfig="parseOnLoad:true"></script>
<script type="text/javascript">
  dojo.require("dojox.image.Gallery");
  dojo.require("dojo.data.ItemFileReadStore");
</script>
#{/set}

#{form @Application.storeImage(), enctype:'multipart/form-data'}
  <div>Title: <input type="text" name="description"></div>
  <div>File: <input type="file" name="image"></div>
  <div><input type="submit" value="Send"></div>
#{/form}

<h1>The gallery</h1>

<div jsId="imageItemStore" dojoType="dojo.data.ItemFileReadStore"url="@{Application.getImages()}"></div>

<div id="gallery1 dojoType="dojox.image.Gallery">
  <script type="dojo/connect">
    varitemNameMap = {
      imageThumbAttr: "thumb",
      imageLargeAttr: "large"
    };
    this.setDataStore(imageItemStore, {}, itemNameMap);
  </script>
</div>

How it works...

The configuration and routes files are already explained above. The controller mainly uses the GridFSHelper and the GridFSSerializer .

The GridfSHelper calls the Morphia plugin to get the database connection. You could also do this by just using the MongoDB driver; however, it is likely that you will use the rest of the Morphia module as well. The getGridFS() method returns the object needed to extract GridFS files from MongoDB. The getFile() method queries for a certain object ID, while the getFiles() method returns all objects because a query by example is done with an empty object. This is the way the standard MongoDB API works as well. The storeFile() method deletes an already existing file (the image file name used when uploading is used here). After deletion it is stored, and its content type is set along with a metadata tag called title. As storing might pose problems (connection might be down, user may not have rights to store, filesystem could be full), an exception can possibly be thrown, which must be caught in the controller.

The Serializer for a GridFSDBFile is pretty dumb in this case. The format of the JSON file is predefined. Due to the use of the Dojo Toolkit, data has to be provided in a special format. This format requires having four properties set for each image:

  • thumb: Represents a URL of a small thumbnail of the image.
  • large: Represents a URL of the normal sized image.
  • link: Represents a URL which is rendered as a link on the normal sized image. Could possibly be a Flickr link for example.
  • title: Represents a comment for this particular image.

For the sake of simplicity, the thumb, large, and link URLs are similar in this special case – in a real world application this would not be an option.

The controller has no special features. After a file upload the user is redirected to the index page. This means only one template is needed. The getImages() method uses the special serializer, but also needs to have an items defined to ensure the correct JSON format is returned to the client. The showImage() method gets the file from the GridFSHelper class and then sets header data as needed in the renderBinary() by calling it with the right arguments. There is no need to set headers manually.

After setting the class attribute to the body tag in the main template, the last thing is to write the index.html template. Here all the needed Dojo JavaScript and CSS files are loaded from the Google CDN. This means that you do not need to download Dojo to your local system. The dojo.require() statements are similar to Java class imports in order to provide certain functionality. In this case the Gallery functionality itself uses a so-called FileItemReadStore in order to store the data of the JSON reply in a generalized format which can be used by Dojo. Whenever you want to support HTML form-based file uploads, you have to change the enctype parameter of the form tag. The rest of the HTML is Dojo specific. The first div tag maps the FileItemReadStore to the JSON controller. The second div tag defines the gallery itself and maps the FileItemReadStore to the gallery, so it uses the JSON data for display.

After you finished editing the templates, you can easily go to the main page, upload an arbitrary amount of pictures, and see it including a thumbnail and a main image on the same page.

There's more...

Of course this is the simplest example possible. There are many possibilities for improvement.

Using MongoDB's REST API

Instead of using the Morphia module, you could also use the MongoDB built-in REST API. However, as it is quite a breeze to work with the Morphia API, there is no real advantage except for the more independent layer.

Resizing images on the fly

You could possibly create thumbnails on the fly when uploading the file. There is a play.libs.Images class which already features a resize() method.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.187.62