Skip to main content

Create a photo album application with Project Zero and REST design principles

Validate Zero as an RIA platform along the way

Dan Jemiolo (danjemiolo@us.ibm.com), Advisory Software Engineer, IBM, Software Group
Dan Jemiolo is an Advisory Software Engineer on IBM's Project Zero team in Research Triangle Park, NC. He is currently working on reusable components for the Zero platform and its service catalog. His previous work includes the design and development of Apache Muse 2.0 and participation in OASIS Web services standards bodies. Dan came to IBM three years ago after earning his Master of Science degree in Computer Science from Rensselaer Polytechnic Institute.

Summary:  One of the main goals of Project Zero is to simplify the creation of rich Internet applications (RIAs). The Flickr photo sharing service is an excellent example of such an application. Designed using REST principles, Asynchronous JavaScript + XML (Ajax) techniques, and dynamic scripting languages, Flickr provides a service that is not only user friendly, but also scalable and extensible. Because Flickr has many of the qualities that authors of other RIAs are striving for, recreating this type of application with Zero would be an excellent way to validate Zero as an RIA platform. In this article, see how to combine existing Zero components to create a photo sharing service that can support many of the same functions provided by Flickr today. Along the way, you'll learn more about RESTful design, connecting components via HTTP, and the use of JavaScript to provide a function that isn't already part of Zero.

Date:  16 Oct 2007
Level:  Intermediate
Activity:  3013 views

Before you get started

This article assumes that you have downloaded Project Zero and either completed the introductory tutorial or written a simple application yourself. You should be familiar with the basic principles of REST and the different types of HTTP methods (GET, POST, etc.). See the Resources section for an introduction to REST.

Introduction to photo sharing application design

Yahoo!'s Flickr photo sharing service (see Resources) is an excellent example of an RIA. Because Flickr has many of the qualities that authors of other RIAs are striving for, recreating such an application with Zero validates that Zero is an excellent platform for developing RIAs. Fortunately, we will not have to create an entire photo sharing service from scratch — the Zero platform includes a number of RIA components such as a blog, a rating system, a profile manager, and more, all of which you can reuse when building your own applications. The following sections discuss the nature of a photo sharing service and show that this type of application has a lot in common with the components that exist in Zero today.

The Project Zero community
Take a stroll around the Project Zero Web site, and see how Project Zero provides a powerful but radically simple development and execution platform for modern Web applications. The active community discusses project development, provides help to developers, and wants to hear your ideas!

Designing a photo sharing application

Creating a photo sharing service might seem like a massive undertaking at first, but if you stop and think about it, you may see that this type of application has a lot in common with two well understood Web 2.0 applications: blogging and file sharing. Flickr is essentially a blogging service that provides a more domain-specific view of its blog posts. Each photo in a user's collection is represented by a blog post, and the photos (blog posts) are organized into albums (blog categories) and augmented with descriptive tags. Further, Flickr allows users to access their actual image files outside of its slick user interface, which implies that the image storage is handled by some high-end file sharing service that is separate from the blog service. Project Zero includes both blog and file share components in its suite of optional libraries, which can be tied together to form a similar photo sharing experience.

The implementation of our photo sharing UI will store photos using the file share component and organize them using the blog component. While users browsing the contents of the file shares or blogs will be able to see their photos there as well, the photo sharing UI is much more photo-centric and user friendly. It's beyond the scope of this article to cover how to create all of the fancy Ajax or Flash-driven widgets that make the Flickr UI so impressive, but it does explain the RESTful interfaces and logic needed to drive the behavior of such widgets. With a well-designed photo sharing service in place, it should be possible to put any type of UI frills on top of it.

Let's take a closer look at the design of Zero's blog and file share components to see how easily they can fit into our photo sharing application.

Use RESTful resources as building blocks

Both the blog and file share components are made up of one or more resource types that can be manipulated using HTTP methods. Each HTTP method represents a read or write operation on a resource, and each HTTP URI represents an individual resource instance. As an example, an HTTP GET against http://www.example.com/blogs/jsmith/posts/our-latest-vacation might return a representation of a blog post by user jsmith entitled Our Latest Vacation. Similarly, an HTTP POST against http://www.example.com/blogs/jsmith might be used to create a new blog post in user jsmith's blog. Table 1 explains how HTTP methods and URIs are used to interact with Zero's blog service, including the format of the data exchanged between client and server. The URI tokens in bold are variables:


Table 1. REST interface for the Zero blog component
URIHTTP MethodData FormatDescription
/blogsPOSTJSONCreate a new blog using the JSON representation sent in the request body
/blogs/blog-nameGETJSONRetrieve the resource definition for the blog with the given name
/blogs/blog-namePUTJSONUpdate the resource definition for the blog with the given name
/blogs/blog-nameDELETENoneDelete the blog with the given name, including all of its posts and comments
/blogs/blog-name/postsPOSTAtomCreate a new blog post on the JSON representation sent in the request body
/blogs/blog-name/posts/post-idGETAtomRetrieve the resource definition for the blog post with the given ID
/blogs/blog-name/posts/post-idPUTAtomUpdate the entire resource definition for the blog post with the given ID
/blogs/blog-name/posts/post-idDELETENoneDelete the blog post with the given ID

When you add in authentication and authorization through HTTP, you can see that this API provides all of the function necessary to manage one or more blogs. The API can be invoked by Ajax toolkits such as Dojo very easily, allowing developers to integrate the response data with UI widgets without a page refresh (see Resources for a link to more information on the Dojo Toolkit). Of course, the API can also be accessed by an ordinary HTTP client, such as Apache's HttpClient (for Java™ technology) or the cURL command-line tool (for C) (see Resources for links). You can even invoke parts of the API using your Web browser's address bar. Blogs are created and modified using the JavaScript Object Notation (JSON) while blog posts are manipulated with Atom (see Resources for links). Table 2 shows a similar API for the file share component, which stores public files and directories:


Table 2. REST interface for the Zero file share component
URIHTTP MethodData FormatDescription
/file-shares/user-namePOSTJSONAdd a new file or directory to the user's file share using its JSON representation
/file-uploads/user-namePOSTHTML form dataAdd a new file or directory to the user's file share using an HTML form
/file-shares/user-name/pathGETJSONRetrieve the file or directory at the given path in the user's file share
/file-shares/user-name/pathPUTJSONUpdate the file or directory at the given path in the user's file share
/file-shares/user-name/pathDELETENoneDelete the file or directory at the given path in the user's file share

Items of note in the file share API include the use of user names to split up the shared directories and the duplicate ways to add a file. The fact that every user has his or her own shared directory will make it easy to identify a place for storing image files on behalf of his or her photo sharing account. The two methods of adding a file through HTTP POST give you flexibility when it comes to designing your photo album UI — you can either use HTML forms to upload the data or you can use non-browser clients similar to Flickr's Uploadr tool (see Resources for a link).

With these APIs in hand, you can now design our photo sharing service as a set of operations on the two components. You do not need to know any of the particulars about the component implementations — all interaction with them will be through their RESTful HTTP interfaces. The next step is to make some concrete decisions on how photo sharing actions map to HTTP requests.

Mapping photo sharing actions to blogs and file shares

You can start designing your application by constructing a table similar to Table 1 and Table 2 and seeing what function is covered by the blog and file share components and what needs to be written. Table 3 provides English-only descriptions of the features you need to support and how they would map to invocations of the component APIs:


Table 3. Mapping photo sharing actions to blogs and file shares
Photo Sharing ActionWhat Happens Under the Covers
Create a new photo sharing accountCreate a new blog to sort the photos
Create a new file share directory for the image files
Create a new photo albumCreate a new blog category
Add a photo to a photo albumUpload a copy of the image file to the file share directory
Create a new blog post with an <img/> tag pointing to the image file
Retrieve all photosRetrieve all blog posts in the photo blog
Retrieve an entire photo albumRetrieve all blog posts in a given category
Retrieve an individual photoRetrieve the photo's blog post
Update an individual photo, including its albumUpdate the photo's blog post and category name
Delete all photosDelete the photo blog
Optionally delete the file share directory with the image files
Delete an entire photo albumDelete the blog category
Delete the blog posts in the category
Optionally delete the image files associated with the blog posts
Delete an individual photoDelete the blog post
Optionally delete the image file associated with the blog post

Table 3 tells you that only a few of your features require multiple operations "under the covers," so the amount of glue code you'll need to write will be minimal. Table 4 is just like Table 3 except that the English descriptions have been replaced with the exact HTTP methods and URIs that need to be used:


Table 4. Mapping photo sharing actions to HTTP requests
Photo Sharing ActionWhat Happens Under the Covers
Create a new photo sharing accountHTTP POST to /blogs

HTTP POST to /file-shares/user-name
Create a new photo albumHTTP POST to /blogs/blog-name/categories
Add a photo to a photo albumHTTP POST to /files-shares/user-name

HTTP POST to /blogs/blog-name/posts
Retrieve all photosHTTP GET on /blogs/blog-name
Retrieve an entire photo albumHTTP GET on /blogs/blog-name/categories/category-name
Retrieve an individual photoHTTP GET on /blogs/blog-name/post/post-id
Update an individual photo, including its albumHTTP PUT to /blogs/blog-name/post/post-id
Delete all photosHTTP DELETE on /blogs/blog-name

HTTP DELETE on /file-shares/user-name/photos
Delete an entire photo albumHTTP DELETE on /blogs/blog-name/categories/category-name

HTTP DELETE on /blogs/blog-name/posts/post-id

HTTP DELETE on /file-shares/user-name/photos/photo-file-name
Delete an individual photoHTTP DELETE on /blogs/blog-name/posts/post-id

HTTP DELETE on /file-shares/user-name/photos/photo-file-name

All the features of one HTTP request can be handled easily with one of the aforementioned Ajax or HTTP client libraries; the multioperation features will require some glue code to ensure that all the HTTP requests are completed successfully (as a "transaction"). At this point, you have not identified any features of the photo sharing service that are independent of blogs or file shares, so you will not have to create any new RESTful resources in your Zero application.

Implementing the photo sharing application

Now that you have an understanding of the concepts behind the photo sharing application, it's time to get your hands dirty. This section assumes that you have created a new Zero application named photo-share using Zero's Eclipse plug-in or command-line tool. The instructions that follow reference the command-line interface, but the Eclipse equivalents are easy to find from your project's context menu.

Installing the blog and file share components

The first order of business is to add the blog and file share components to your application. Open the application's Ivy file at /config/ivy.xml and add the following lines:


Listing 1. Adding blog and file share components as dependencies
      
<dependency org="zero" name="zero.services.blog" rev="1.0.0+"/>
<dependency org="zero" name="zero.services.share" rev="1.0.0+"/>
      

Once you've added the XML in Listing 1, you must run zero resolve from the command line to finish installing the two components. Even though their actual code and artifacts have not been added to your project, references to the components from your own code will work at run time thanks to Zero's dependency resolution mechanism.

Using JavaScript to tie the pieces together

The photo sharing actions that map to a single HTTP request (see Table 4) can be implemented very easily using the Dojo JavaScript toolkit. Zero includes the latest version of Dojo (0.4.3), and you can add it to your application by adding the XML from Listing 2 to your Ivy file:


Listing 2. Adding a Dojo dependency
      
<dependency org="dojo" name="dojo" rev="0.4.3+"/>
      

Dojo makes it easy to transfer HTML form data into HTTP requests, and that is all you will need to do for these single-request actions. The code in Listing 3 shows an HTML form for entering the name of a new photo album and a JavaScript function that creates a new photo album using HTTP POST. Notice how the data taken from the form is added directly to the request body as a JSON object; the implementation of zero.services.blog is expecting a JSON object and will process your request without massaging the data at all.


Listing 3. Creating a new photo album with HTML and JavaScript
    
<script type="text/javascript" src="/dojo.js">
</script>
    
<script>
dojo.require("dojo.io");
dojo.require("dojo.json");
dojo.require("dojo.widget.Form");
    
function createAlbum() 
{
    var form = dojo.widget.byId("CreateAlbumForm");
    var albumData = form.getValues();
        
    var blogName = getPhotoShareName();

    dojo.io.bind({
        url: '/resources/blogs/' + blogName + '/categories',
        method: 'POST',
        sync: false,
        mimetype: 'text/json', 
        contentType: 'text/json',
        postContent:  dojo.json.serialize(albumData),
        load: function(type, data) {
            alert("The new album was added successfully.");
        },
        error: function(type, err) {
            alert(dojo.errorToString(err)); 
        }
    });
}
    
function getPhotoShareName()
{
    return <%= _gc.get("/request/subject/remoteUser") + "-photos"; %>
}
    
</script>
    
...
    
<form dojoType="form" id="CreateAlbumForm" name="CreateAlbumForm">
  <table cellpadding="10" cellspacing="0" border="0" width="50%">
    <tr>
      <td><b>New Photo Album:</b></td>
      <td><input type="text" size="64" name="categoryid"/></td>
    </tr> 
  </table>
  <br/>
  <input type="button" onClick="createAlbum();" value="Submit" />
</form>
  

As you can see, the createAlbum() function takes the JSON data directly from the HTML form (form.getValues()) and adds it to the HTTP request as a parameter to dojo.io.bind(). The request is an HTTP POST with the URI /blogs/blog-name/categories, just as Table 4 described. Notice that even though the HTML tells users they are creating a new photo album, under the covers you are creating a new blog category to represent it; similarly, the name of the users' photo share (provided by the getNameOfPhotoShare() method) is used as the name of a blog when constructing the HTTP request URI. The users do not have to know anything about your application's use of blogs — all they know is that they now have a way to organize some of their photos.

Implementing the rest of the single-request actions is as simple as copying the createAlbum() method and modifying it so that it has a new name and uses a different HTTP method (for example, deleteAlbum() and method: 'DELETE'). In all cases, you will respond to the click of a form button or a page reload to GET, POST, PUT, or DELETE content using the URIs in Table 4.

Handling transactions in JavaScript

The single-request actions aren't hard to implement once you're familiar with the dojo.io.bind() function, but handling the multirequest actions requires a bit more thought. Listing 4 shows an HTML form and JavaScript function for handling the creation of a new photo sharing account. The createPhotoShare() method sends two HTTP POST requests: one to create a directory on the user's file share and another to create a blog for organizing the photos. Notice that the error handler for the latter undoes the work of the former so that data is not left in an inconsistent state.


Listing 4. Creating a new photo share with HTML and JavaScript
    
<script type="text/javascript" src="/dojo.js">
</script>
    
<script>
dojo.require("dojo.io");
dojo.require("dojo.json");
dojo.require("dojo.widget.Form");
   
function createPhotoShare() 
{
    var userName = getUserName();
    
    //
    // create JSON objects to represent shared directory and blog
    //
    
    var directoryData = {
        path = userName + '-photos', 
        contenttype = 'directory', 
        owner = userName
    };
        
    var blogData = {
        DESCRIPTION : 'The photo blog for ' + userName,
        AUTHOR: userName, 
        HANDLE: userName + '-photos',
        TITLE: 'The photo blog for ' + userName
    };
        
    dojo.io.bind({
        url: '/resources/file_shares/' + userName,
        method: 'POST',
        sync: false,
        mimetype: 'text/json', 
        contentType: 'text/json',
        postContent:  dojo.json.serialize(directoryData),
        load: function(type, data) {
            alert("The new photo directory was created successfully.");
        },
        error: function(type, err) {
            alert(dojo.errorToString(err)); 
        }
    });
    
    dojo.io.bind({
        url: '/resources/blogs',
        method: 'POST',
        sync: false,
        mimetype: 'text/json', 
        contentType: 'text/json',
        postContent:  dojo.json.serialize(blogData),
        load: function(type, data) {
            alert("The new photo blog was created successfully.");
        },
        error: function(type, err) {
            alert("Could not complete creation of photo share.");
            deletePhotoShare();
        }
    });
}
    
function deletePhotoShare()
{
    var user = getUserName();
        
    dojo.io.bind({
        url: '/resources/file_shares/' + user + '/' + user + '-photos',
        method: 'POST',
        sync: false,
        headers: { 'X-Method-Override' : 'DELETE' }, 
        load: function(type, data) {
            alert("The photo directory was deleted successfully.");
        },
        error: function(type, err) {
            alert(dojo.errorToString(err)); 
        }
    });
    
    dojo.io.bind({
        url: '/resources/blogs/' + user + '-photos',
        method: 'POST',
        sync: false,
        headers: { 'X-Method-Override' : 'DELETE' }, 
        load: function(type, data) {
            alert("The photo blog was deleted successfully.");
        },
        error: function(type, err) {
            alert(dojo.errorToString(err));
        }
    });
}
    
function getUserName()
{
    return <%= _gc.get("/request/subject/remoteUser"); %>
}
    
</script>
    
...
    
<form dojoType="form" id="CreateAlbumForm" name="CreateAlbumForm">
  <input type="button" onClick="createPhotoShare();" value="Create My Photo Share!" />
</form>
  

There's a lot to understand in Listing 4, so pay careful attention. The HTML form is just a button that users can click to create a photo sharing account in their name. Your default behavior is to name the users' account (and, by association, their file share and blog) using the pattern user-photos, where user is the user's login ID. You use this account name to construct HTTP POST requests in the createPhotoShare() function and HTTP DELETE requests in the deletePhotoShare() function. In both functions, you use dojo.io.bind() to fill in the HTTP request data and handle the responses. The two things that are most interesting about this code are the aforementioned transaction management and the creation of the JSON objects to represent the new shared directory and blog, both of which occur in createPhotoShares(). The latter requires you to know what fields the JSON objects must have in order to be accepted by the components; you can find this information in the components' API documentation on projectzero.org (see Resources).

The other multirequest actions in Table 4 require requests that look a little different than those in Listing 4, but the structure will remain the same: multiple calls to dojo.io.bind() with error handlers that undo whatever work has already been done. The only exception to this will be the actions associated with photo or account deletion; once you delete part of the account's data, you'll need to complete the task even if some parts of it fail. You can avoid this by performing all the deletion tasks in a server-side script (where you can perform real database transactions), but that optimization is beyond the scope of this article.

Take a moment to notice that, once again, you are using photo-centric names and values against a back end that is made up solely of blogs and shared directories. When users visit your photo sharing Web site, they will not see any reference to blogs or file shares unless they view your HTML source code, making the application appear very cohesive despite the fragmentation underneath. Finally, the fact that your two components are useful outside the area of photo sharing makes your application even more powerful than originally planned. Because the components are already installed, there's no reason you can't use them to host "regular" blogs and non-photo file shares for your users, all in one Web site. Neat!

Conclusion

You have explored how Zero components can be tied together using their RESTful interfaces to provide rich and powerful Internet applications. This approach to the photo sharing application was built upon the fundamentals of service-oriented architecture and RESTful resources, leading to an implementation that reused code as much as possible and keeping most of the custom code in your user interface. Like REST itself, your photo sharing application is not really a concrete set of code but more an architectural style. Developers building other types of applications with Zero should look into and consider the components Zero has to offer when planning their implementations.


Resources

Learn

Get products and technologies

  • Download Project Zero and start applying the skills learned in this article.

Discuss

About the author

Dan Jemiolo

Dan Jemiolo is an Advisory Software Engineer on IBM's Project Zero team in Research Triangle Park, NC. He is currently working on reusable components for the Zero platform and its service catalog. His previous work includes the design and development of Apache Muse 2.0 and participation in OASIS Web services standards bodies. Dan came to IBM three years ago after earning his Master of Science degree in Computer Science from Rensselaer Polytechnic Institute.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development, WebSphere
ArticleID=261831
ArticleTitle=Create a photo album application with Project Zero and REST design principles
publish-date=10162007
author1-email=danjemiolo@us.ibm.com
author1-email-cc=ruterbo@us.ibm.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers