Sunday, December 11, 2011

Java Pet Store 2.0 - the architecture (part 1)

To my surprise, the Java Pet Store 2.0 page, even though recently updated, still doesn't mention much about the design of the application.
So here we go, below you find some diagrams and explanations over how it's built and how it works. I'll continue the story in future posts, here I start with the overview and show how the catalog gets displayed.

First, check out this part of the web.xml file:

<servlet>
        <display-name>ControllerServlet</display-name>
        <servlet-name>ControllerServlet</servlet-name>
        <servlet-class>com.sun.javaee.blueprints.petstore.controller.ControllerServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ControllerServlet</servlet-name>
        <url-pattern>/catalog</url-pattern>
    </servlet-mapping>

It tells that the ControllerServlet needs to be used for URLs that match the pattern "/catalog".
For example, when following the link "Dogs" on the main page, we navigate to:
http://localhost:8087/petstore/faces/catalog.jsp?catid=Dogs
(I have Glassfish running on localhost:8087)
Since this matches the "/catalog" pattern, ControllerServlet.service() is triggered, as shown in Figure 2.
The ControllerServlet implements the FrontController J2EE pattern and delegates to various actions (see Figure 1 and Figure 2), depending on the servlet path. In this case the servlet path is "/catalog" and it maps to CatalogXmlAction. The latter reads the "command" parameter from the request and, because this one is "categories", it calls the CatalogFacade to get the categories via JPA (note in Figure 1 that "Category" is a JPA Entity).


Figure 1: The main entities involved in fetching the catalog

Figure 2: Displaying the catalog

The CatalogXml action, once it has the categories, it writes them to the response (HttpServletResponse) in JSON format (see below).
So how come, given this JSON format, the categories and the pets are so nicely displayed in the page (see Figure 3) still ?  Well, this is done with a bit of Dojo/JavaScript magic. 


[{"id":"CATS","catid":"CATS","name":"Cats","description":"Loving and finicky friends","imageURL":"cats_icon.gif",
"products": [{"id":"feline01","catid":"CATS","name":"Hairy Cat","description":"Great for reducing mouse populations",
"imageURL":"cat1.gif"},{"id":"feline02","catid":"CATS","name":"Groomed Cat","description":"Friendly house cat keeps you 
away from the vacuum","imageURL":"cat2.gif"}]},
etc.
]




Figure 3: The Pet Store application - after choosing the categories

Take a look at catalog.js, which is used in catalog.jsp to format the page:

function loadAccordion () {
        // go out and get the categories
        // this should be made more geric
        var bindArgs = {
            url:  applicationContextRoot + "/catalog?command=categories&format=json",
            mimetype: "text/json",
            load: function(type,json) {
               ac.load(json);
               processURLParameters();
             },
             error: ajaxBindError
        };
        dojo.io.bind(bindArgs);
    }


This method is called from initCatalog() in catalog.js, which in turn is called in catalog.jsp:

<script type="text/javascript">
    dojo.event.connect(window, "onload", function(){initCatalog();});
</script>


Finally, loadAccordion() calls ac.load(json) and ac.showCategory(params.catid), where "ac" is AccordionMenu() (the menu with categories in Figure 3), which again, sends us to:


this.load = function(lcategories) {
        categories = lcategories;
        // create all the rows
        for (var l=0; l &lt; categories.length; l++) {
            var row = createRow(l,"accordionRow", ITEM_HEIGHT);
            createLinks(row.div, categories[l].name, l, "accordionLink");
            divs.push(row);
        }
    }

Here, createLinks() makes the links on the left-hand side menu ("Cats", "Dogs", etc.), using categories[l].name as parameter. "Categories" come from the JSON shown above, retrieved in loadAccordion(): url:  applicationContextRoot + "/catalog?command=categories&format=json".

this.showCategory = function(catid) {
        for (var l=0; l < categories.length; l++) {
            if (catid == categories[l].name) {
                // now tell the scroller to load the first product
                initiateExpansion(l);
                if (categories[l].products[0]) {
                    dojo.event.topic.publish("/catalog", {type:"showProducts", productId:categories[l].products[0].id});
                } 
                break;
            }
        }
    }

Notice here "type" and "productId" labels. Back in catalog.js, we do:
dojo.event.topic.subscribe("/catalog", this, handleEvent);

So here we subscribe to the "/catalog" events with the callback "handleEvent", which does:

else if (args.type == "showProducts") {
          is.reset();
          populateItems(args.productId, 0, 0, true);
      }


Here, args.type is the label "type" we published above and args.productId is the label "productId". With the argument args.productId, "populateItems()" displays the images, etc. In order to do that, it uses DOM fields from catalog.jsp, e.g.:


var targetElement = document.getElementById("bodySpace");

To be continued ....