DekGenius.com
Previous Section  < Day Day Up >  Next Section

10.2 Replacing the Controller

First, let's replace the SearchProductsController. Here's the main method of that class:

public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse 
                                  response) throws Exception {
    if (request.getParameter("search") != null) {
        String keyword = request.getParameter("keyword");
        if (keyword == null || keyword.length( ) == 0) {
            return new ModelAndView("Error", "message", 
            "Please enter a keyword to search for, 
            then press the search button.");
        }
        else {
            PagedListHolder productList = new PagedListHolder(
                this.petStore.searchProductList(keyword.toLowerCase( )));
            productList.setPageSize(4);
                request.getSession( ).setAttribute(
            "SearchProductsController_productList", productList);
            return new ModelAndView("SearchProducts", "productList", 
                productList);
        }
    }
    else {
        String page = request.getParameter("page");
        PagedListHolder productList = (PagedListHolder) 
           request.getSession( ).getAttribute("SearchProductsController_productList");
            
        if ("next".equals(page)) {
            productList.nextPage( );
        }
        else if ("previous".equals(page)) {
            productList.previousPage( );
        }
        return new ModelAndView("SearchProducts", "productList", productList);
    }
}

The method returns a new instance of ModelAndView and Spring uses it to determine which JSP to load and how to wire data up to it. The method takes an HttpServletRequest and HttpServletResponse in order to interact directly with the HTTP messages.

The first thing the method does is make sure the user entered a search term. If not, it displays an error to the user; if so, it creates a PagedListHolder called productList with a maximum page size (number of rows per page) set to four. Finally, it calls the petStore instance's searchProductList method, which calls to ProductsDao and finally returns the HashMap of Product instances. The second clause is for when the user clicks the Next Page or Previous Page buttons on the paged list.

10.2.1 Rewrite or Replace?

The next question a conscientious programmer should ask is, does it make more sense to rewrite this class to make use of the Spider, or to write an entirely new controller? In order to answer that question, we need to consider three more-specific questions first:

  1. Do we have access to the original source? Now that we have the jPetStore application, do we control the source, or is it all binary? If we don't control the source, we can short-circuit the rest of the decision. We can only replace the class; we can't rewrite it.

  2. Will we ever need to use the original service again? Assuming we have the source and can rewrite the class, can we foresee ever needing to revert to or make use of the database-search functionality? For the sake of flexibility, we usually want to retain old functionality unchanged, which means we want to replace, not rewrite. However...

  3. Does the current class implement an easily reused interface? If we are going to replace the class, how much work will we have to do to get the application to recognize and accept your new class? Think of this as an organ transplant; how much work and medication has to go into the host body to keep it from rejecting the new organ? Will our changes be localized around the new class or more systemic?

Here's the answer to these questions: yes, we have the source code; yes, we'll want access to retain the potential for using the old service; and yes, the controller implements a very simple interface. The controller only needs to implement a single method, handleRequest, which takes an HttpServletRequest and a HttpServletResponse and returns a ModelAndView. This means the jPetStore application doesn't need any systemic changes in order to use our new controller, as long as we support that interface.

10.2.2 Implementing the Interface

To replace this class, we're going to write our own controller class called SearchPagesController. It must implement the Controller interface, which defines our handleRequest method.

public class SearchPagesController implements Controller {
    ...
}

Here's our controller's handleRequest method:

public ModelAndView handleRequest(HttpServletRequest request, 
                                  HttpServletResponse response) throws Exception {
    if (request.getParameter("search") != null) {
        String keyword = request.getParameter("keyword");
        if (keyword == null || keyword.length( ) == 0) {
            return new ModelAndView("Error", "message", "Please enter a 
            keyword to search for, then press the search button.");
        }
        else {
               ConfigBean cfg = new ConfigBean( );
                    String indexpath = "";
                    try
                    {
                        indexpath = cfg.getCurIndexPath( );
                    }
                    catch(Exception ex)
                    {
                      return new ModelAndView("Error", "message", 
                                              "Could not find current index path.");
                    }

                    QueryBean qb = new QueryBean(indexpath, keyword, "contents");

                    qb.execute( );

                    HashMap hits = new HashMap(qb.getResults( ).length);
                    for(int i =0;i<qb.getResults( ).length;i++)
                    {
                      hits.put("hits", qb.getResults( )[i]);
                    }
                    return new ModelAndView("SearchProducts", hits);
        }
    }
}

For our search functionality, we won't use the paged results. We simply list all the results on a single page; as a result, we don't have to deal with the Next Page and Previous Page code. Our controller again checks for null keywords and returns an error if it finds them empty. Otherwise, the service is used almost identically as the console application in the Chapter 9 was used. First, create an instead of ConfigBean to find the most current index of the site, then create a QueryBean based on that index path. Finally, execute the query and put all the HitBean instances into a HashMap to return to the View.

The usage pattern is identical to that in the last chapter; the only difference is the format of our returned data. Instead of passing the native array of HitBeans back, the ModelAndView object requires a HashMap. It's easy enough to create the one from the other, and now we have an entirely new access point for the Spider application with almost no work.

There is one last detail we need to work out. The original SearchProductsController has a field called petStore of type PetStoreFacade that the Spring framework populates for it. In order to be a complete replacement for the original, our new controller needs to expose the same property and accessor methods, even though they aren't officially found on a standalone interface anywhere in the application. You will often find examples of this when you're extending or modifying an application.

private PetStoreFacade petStore;

    public void setPetStore(PetStoreFacade petStore) {
        this.petStore = petStore;
    }

10.2.3 Registering Our New Class with jPetStore

Finally, we alert jPetStore to the new controller's existence. If jPetStore is not coded for extensibility, we have to modify the application code in order to get it to work. For instance, if there are methods of jPetStore that create instances of SearchProductsController directly, we must change each of those lines to create a SearchPagesController instead.

It turns out, however, that jPetStore is quite ready for extensibility—partly because it is based on the Spring framework. In order to tell jPetStore about our new controller, we modify a single configuration file (petstore-servlets.xml). This file tells Spring what objects to create and how to wire them together to make a sensible application. Now, we just need to find the configuration setting used to launch the SearchProductsController and point it to our new SearchPagesController instead.

<bean name="/shop/searchProducts.do" 
    class="org.springframework.samples.jpetstore.web.spring.SearchPagesController">
    <property name="petStore"><ref bean="petStore"/></property>
</bean>

We're telling the application to map requests for "/shop/searchProducts.do" to a new instance of SearchPagesController. At the same time, we tell it, provide the SearchPagesController with the current instance of petStore (in a property called petStore).

10.2.3.1 Principles in action
  • Keep it simple: the controller logic is a simple invocation of Spider; the controller interface is very simple (one method)

  • Choose the right tools: Spring and the Spider

  • Do one thing and do it well: since the Spider is so well-encapsulated, it's easy to add to an existing service; the controller deals with invoking the Spider and the JSP only needs to display the results—MVC pattern well-demonstrated

  • Strive for transparency: the site doesn't care how it is indexed; it can easily switch between data-driven and HTML-driven search technologies

  • Allow for extension: we quickly expanded our search capabilities by adding a new tool with minimal code; the configuration abilities of jPetStore allow for no-code recognition of new service

    Previous Section  < Day Day Up >  Next Section