Ask a question
Wednesday, 16 September 2009 16:23

Mobile Design Patterns: The Smart Ticket Blueprint

Rate this item
(0 votes)

5.1 Getting Started

The Smart Ticket application is available from Sun Microsystems' blueprints Web site (see "Resources"). The zip package contains source code; ANT build scripts; and prebuilt, deployable application binaries.


The Smart Ticket sample contains a J2EE component for the enterprise back end and a J2ME component for the mobile front end. Running the application requires a J2EE application server and a MIDP v2.0-compatible device (or emulator) with Internet connectivity. For learning and testing purposes, we can run both the J2EE server and MIDP emulator on the same computer as follows.

  1. Set up the following environment variables:

    • JAVA_HOME: The JDK installation directory (JDK v1.4.1 and above required)

    • J2EE_HOME: The J2EE reference implementation (RI) installation directory (J2EE v1.3.1 and above required)

    • J2MEWTK_HOME: The J2ME Wireless ToolKit installation directory (J2ME WTK v2.0 and above required)

  2. Start the J2EE server using the following two commands:

    J2EE_HOME\bin\cloudscape -start
    
    J2EE_HOME\bin\j2ee -verbose
    
  3. Deploy the J2EE application using the following script. The setup script in turn invokes the corresponding ANT task in setup.xml:

    setup deploy
  4. Point your browser to http://localhost:8000/smartticket and click on the populate database link to import the mock theater and movie data to the Smart Ticket database. This could be a very slow process on older computers, so be patient! The mock data set contains theater information in two zip code areas: 95054 and 95130.

  5. Start the J2ME WTK v2.0 and run MIDlet smart_ticket-client.jad.

Now, you are ready to order some movie tickets from the phone emulator!

Note

If you want to run the MIDP client on an actual smart phone device, the J2EE server must run on an Internet-accessible computer and you must configure the MIDP client for the correct server IP address.

5.2 Smart Ticket in Action

The Smart Ticket client allows the user to manage user preferences, browse movie schedules, order tickets, rate watched movies, and pre-download the schedule. We discuss those features one by one in this section.

Note

The Smart Ticket blueprint is designed for educational purposes. It demonstrates the use of design patterns. As developers, we should judicially use patterns learned from the Smart Ticket. The overuse of design patterns results in unnecessary abstraction layers and produces slow and large applications.

5.2.1 Manage User Preferences

When the user starts the MIDP client for the first time, she will be asked to create a profile. The profile includes two types of information:

  • Account credentials: The username, password, and optional credit card numbers.

User preferences: The theater search zip codes, favorite day of the week, and preferred seating. Figure 5.1 shows how to manage user preferences.
 

Figure 5.1. Manage user preferences.
 

After the user submits the profile, a corresponding user account is created on the J2EE server. The preference information is cached on the device. The user could configure the MIDP client to cache the account credentials so that she does not need to manually sign in every time she wants to purchase tickets or submit movie ratings. User preferences can be modified at any time through the MIDP UI.

Note

The Smart Ticket client does not encrypt the account and preference information stored on the device. That could create problems if the device is lost.

5.2.2 Search and Purchase Tickets

Once logged in, the user can browse theaters, movies, and show times in her zip code areas. This process involves a series of real-time queries to the J2EE server. Once she selects a show, she will be asked to select currently available seats from an interactive seating map to make reservations. The reservation is persisted to the server database.

5.2.3 Rate Movies

The user can rate movies she has seen . The ratings are not immediately submitted to the server. They are cached on device and can be synchronized to the server upon the user request. That allows the user to rate movies even when the phone is out of network range (for example, in a shielded cinema building!). The synchronization agent is smart: When the same user rates the same movie multiple times, it resolves the issue by keeping only the most recent rating in the backend database.

5.2.4 Cache Theater Schedules

Smart Ticket allows the user to download a theater's schedule to the mobile client. The cached schedule enables offline browsing and improves the performance by reducing network round trips. The user can delete or re-download the schedule as needed 

5.3 Important Architectural Patterns

Smart Ticket utilizes several architectural design patterns, which are commonly used by enterprise architects. It is important for enterprise mobile developers to understand those patterns.

5.3.1 The Overall MVC Pattern

The overall architecture of the Smart Ticket application follows the Model-View-Controller pattern. According to Martin Fowler in his Patterns of Enterprise Application Architecture, the MVC pattern "splits user interface interaction into three distinct roles." In an MVC application, the view and controller components work together as the UI, which primarily concerns how to present the information to the user through a series of displays and interactions. The model component represents the domain model. The model's primary concern is business logic and machine-to-machine interactions (e.g., database access). The use of the MVC patterns brings some important benefits:

  • Since the presentation and the underlying data model are separated, we can employ different experts to work on different parts of the code. For example, a UI expert can design the views while a database expert optimizes the database connections at the same time.

  • MVC allows us to develop multiple views for the same model. For example, the Smart Ticket v2.0 EA and v1.2 applications have the exact same model layer. The MVC pattern allows Sun developers to rewrite the UI for MIDP v2.0 in a short period of time.

  • Nonvisual objects in the model layer are easier to test using automatic tools than are the UI components.

In the Smart Ticket application, the MVC pattern is implemented as follows:

  • Model: Classes in the model layer contain all the business logic. In fact, the entire J2EE server component, on-device caches, and communication classes all belong to the model layer. The most notable design pattern in the model layer is the facades, which we discuss in the next sections.

  • View: Each interactive screen is represented by a view class. There are 17 view classes in the Smart Ticket v2.0 EA client. Once the user generates a UI event (e.g., by pressing a button or selecting an item from a list), the view class's event handler captures the event and passes it to the controller class. Listing 5.1 demonstrates the UI classes for the screen to confirm ticket purchase.

  • Controller: The controller class knows all the possible interactions between the user and the program. In the Smart Ticket application, the controller is the UIController class (Listing 5.2). It has one method for each possible action (e.g., purchaseRequested()). The action method often starts two new threads: one to perform the action in the background and the other to display a progress bar for the user. The background action thread is represented by the EventDispatcher class. The EventDispatcher.run() method contains a long list of switch statements that invoke the corresponding methods in the model layer to perform the action. When the model method returns, the controller displays the next UI screen using the appropriate view class.

Listing 5.1. The ConfirmTicketUI class represents the screen for confirming the ticket purchase
package com.sun.j2me.blueprints.smartticket.client.midp.ui

public class ConfirmTicketUI extends Form

           implements CommandListener, ItemCommandListener {



  private UIController uiController;

  private Command cancelCommand;

  private Command confirmCommand;

  private StringItem theater, movie, showTimeStr, seatsStr;

  private StringItem cost, totalCost, placeOrderBtn;



  public ConfirmTicketUI(UIController uiController) {

    super(uiController.getString(UIConstants.CONFIRM_TITLE));



    this.uiController = uiController;



    createItems();

    append(theater); append(movie); append(showTimeStr);

    append(seatsStr); append(cost); append(totalCost);

    append(placeOrderBtn);



    confirmCommand =

       new Command(uiController.getString(UIConstants.CONFIRM),

                    Command.OK, 5);

    cancelCommand =

        new Command(uiController.getString(UIConstants.CANCEL),

                    Command.EXIT, 5);

    addCommand(confirmCommand);

    addCommand(cancelCommand);

    setCommandListener(this);

    placeOrderBtn.setDefaultCommand(confirmCommand);

    placeOrderBtn.setItemCommandListener(this);

  }



  public void init(String theaterName, String movieName,

                            int[] showTime, Seat[] seats) {

    // Set the display strings to the correct values

  }



  // Command callback for UI events for text button "placeOrderBtn"

  public void commandAction(Command command, Item item) {

    if (command == confirmCommand) {

      uiController.purchaseRequested();

    }

  }



  // Command callback for UI events on the command buttons

  public void commandAction(Command command, Displayable displayable) {

    if (command == cancelCommand) {

        uiController.mainMenuRequested();

    } else if (command == confirmCommand) {

        uiController.purchaseRequested();

    }

  }

}

Listing 5.2. Process the purchaseTickets action in the UIController class in the controller layer
package com.sun.j2me.blueprints.smartticket.client.midp.ui;

public class UIController {



  // references to all UI classes

  // ... ...



  public UIController(MIDlet midlet, ModelFacade model) {

    this.display = Display.getDisplay(midlet);

    this.model = model;

  }



  // ... ...



    public void purchaseRequested() {

    runWithProgress(

       new EventDispatcher(EventIds.EVENT_ID_PURCHASEREQUESTED,

                           mainMenuUI),

       getString(UIConstants.PROCESSING), false);

  }



  class EventDispatcher extends Thread {

    private int taskId;

    private Displayable fallbackUI;



    EventDispatcher(int taskId, Displayable fallbackUI) {

      this.taskId = taskId;

      this.fallbackUI = fallbackUI;

      return;

    }



    public void run() {

      try {

        switch (taskId) {



          // cases ... ...



          case EventIds.EVENT_ID_PURCHASEREQUESTED: {

            model.purchaseTickets(reservation);

            purchaseCompleteUI.init(reservation.getId(),

                                   selectedTheater.getName(),

                                   selectedMovie.getTitle(),



                                   selectedShowTime);

            display.setCurrent(purchaseCompleteUI);



            break;

          }



          // Other cases ... ...



        }

      } catch (Exception exception) {

        // handle exceptions

      }

    } // end of run() method

  } // end of the EventDispatcher class

}

5.3.2 The Clientside Facade

The facade pattern is a structural pattern that provides a simple interface for complex subsystems. In the Smart Ticket application, the clientside subsystems in the model layer, such as the LocalModel, RemoteModelProxy, and SynchronizationAgent classes, are behind the facade class ModelFacade (Listing 5.3), which is the entry point from the controller to the model. The ModelFacade class contains one method for each action in the model layer. It delegates the actions to the subsystems as follows.

  • The LocalModel class handles actions that access the local on-device storage. For example, the purchaseTickets() method adds the purchased movie to the on-device rating list. The addMovieRating() action method in the LocalModel class is called.

  • The RemoteModelProxy class, which implements the RemoteModel interface, handles actions that require access to the remote J2EE server. For example, if the user decides to purchase tickets (reserveSeats() and purchaseTickets()), the transaction has to be done on the server side and be persisted to the database through the RemoteModelProxy. Action methods in the RemoteModelProxy class invoke remote procedure calls (RPC) to the remote facade on the server side. The details of the remote facade and the RPC format are discussed later in this chapter.

  • The SynchronizationAgent class handles all synchronization actions from the local data storage to the remote server. In the case of the Smart Ticket application, it handles only the movie ratings synchronization. It has two action methods: The synchronizeMovieRatings() method synchronizes the ratings; the commitMovieRatings() method commits the resolved synchronization requests to the back end and updates the content of the local store.

The three interactions are illustrated in the following code snippet (Listing 5.3).

Listing 5.3. The ModelFacade class
package com.sun.j2me.blueprints.smartticket.client.midp.model;



public class ModelFacade {



  private SynchronizationAgent syncAgent;



  private RemoteModelProxy remoteModel;

  private LocalModel localModel;





  // Action methods ... ...

  public Reservation reserveSeats(String theaterKey,

        String movieKey, int[] showTime, Seat[] seats)

                        throws ApplicationException {

    try {

      return remoteModel.reserveSeats(theaterKey,

                         movieKey, showTime, seats);

    } catch (ModelException me) {

      // ... ...

    }

  }



  public void purchaseTickets(Reservation reservation)

                         throws ApplicationException {

    try {

      remoteModel.purchaseTickets(reservation.getId());



      // Purchased movies are eligible for rating.

      localModel.addMovieRating(

        new MovieRating(

          remoteModel.getMovie(reservation.getMovieId()),

                    reservation.getShowTime()));

    } catch (ModelException me) {

      // ... ...

    }

    return;

  }



  public void synchronizeMovieRatings(

                    int conflictResolutionStrategyId)

                          throws ApplicationException {

    try {

      syncAgent.synchronizeMovieRatings(conflictResolutionStrategyId);

      return;

    } catch (ModelException me) {

      // ... ...

    }

  }

  // ... ...

}

 

5.3.3 The Serverside Facade

One of the most important benefits of the facade pattern is that it reduces network round trips between remote systems. A properly designed facade allows us to use fine-grained objects in the subsystems yet still have a coarsegrained, simple network interface. It is especially important for mobile applications, since the wireless network is very slow.

When an RPC is made from the RemoteModelProxy to the server side, the HTTP servlet SmartTicketServlet (Listing 5.4) invokes the corresponding action method in Session EJB SmartTicketFacadeBean (Listing 5.6) through a business delegate object SmartTicketBD (Listing 5.5). Depending on the nature of the action, it is further delegated to either TicketingBean or SynchronizingBean, both of which are session EJBs too. The application data on the server side is persisted to the relational database through an array of Container Managed Persistence (CMP) v2.0 entity EJBs.

Listing 5.4. The gateway servlet SmartTicketServlet
package com.sun.j2me.blueprints.smartticket.server.web.midp;



public class SmartTicketServlet extends HttpServlet {



  public static final String SESSION_ATTRIBUTE_SMART_TICKET_BD =

   "com.sun.j2me.blueprints.smartticket.server.web.midp.SmartTicketBD";



  protected void doPost(HttpServletRequest request,

                        HttpServletResponse response)

                        throws ServletException, IOException {

     HttpSession session = request.getSession(true);

        SmartTicketBD smartTicketBD =

(SmartTicketBD) session.getAttribute(SESSION_ATTRIBUTE_SMART_TICKET_BD);



     // Calls handleCall() method and encode the URL for

     // session tracking

  }



  public int handleCall(SmartTicketBD smartTicketBD, InputStream in,

                         OutputStream out) throws IOException,

                         ApplicationException {

    // Identifies the requested action method



    // Execute the method through a list of switch -- case statements

    switch (method) {



    // ... ...



    case MessageConstants.OPERATION_GET_MOVIE:

         getMovie(smartTicketBD, call, successfulResult);

    break;



    // ... ...



    }

  }

}

Listing 5.5. The business delegate class SmartTicketBD
package com.sun.j2me.blueprints.smartticket.server.web.midp;



public class SmartTicketBD implements RemoteModel {

  public static final String EJB_REF_FACADE = "ejb/SmartTicketFacade";

  private SmartTicketFacadeLocal facade;

  private ServletContext servletContext = null;



  public SmartTicketBD(ServletContext servletContext)

        throws ApplicationException {

    this.servletContext = servletContext;



    try {

      Context context =

         (Context) new InitialContext().lookup("java:comp/env");

      facade =

 ((SmartTicketFacadeLocalHome)context.lookup(EJB_REF_FACADE)).create();



      return;

    } catch (Exception e) {

      throw new ApplicationException(e);

    }

  }



  public Movie getMovie(String movieKey)

            throws ModelException, ApplicationException {

    try {

      MovieLocal movieLocal = facade.getMovie(movieKey);

      Movie movie = new Movie(movieLocal.getId(),

                              movieLocal.getTitle(),

                              movieLocal.getSummary(),

                              movieLocal.getRating());



      return movie;

    } catch (SmartTicketFacadeException stfe) {

      throw new ModelException(ModelException.CAUSE_MOVIE_NOT_FOUND);

    } catch (Exception e) {

      throw new ApplicationException(e);

    }

  }



  // Other action methods in RemoteModel interface



}

Listing 5.6. The facade session bean SmartTicketFacadeBean
package com.sun.j2me.blueprints.smartticket.server.ejb;



public class SmartTicketFacadeBean implements SessionBean {



  // ... ...



  public void ejbCreate() throws CreateException {

    // ... ...

    Context context =

        (Context) new InitialContext().lookup("java:comp/env");

    ticketingHome =

        (TicketingLocalHome) context.lookup(EJB_REF_TICKETING);

    synchronizingHome =

      (SynchronizingLocalHome) context.lookup(EJB_REF_SYNCHRONIZING);

    // ... ...

  }



  public MovieLocal getMovie(String movieId)

          throws SmartTicketFacadeException {

    try {

      return movieHome.findByPrimaryKey(movieId);

    } catch (FinderException fe) {

      throw new SmartTicketFacadeException("No matching movie.");

    }

  }



  public void purchaseTickets(String reservationId)

            throws SmartTicketFacadeException {

    if (ticketing != null) {

      ticketing.purchaseTickets(reservationId);

      return;

    }

    throw new SmartTicketFacadeException("User not logged in.");

  }



  public MovieRatingData[] synchronizeMovieRatings(

                      MovieRatingData[] movieRatings,

                      int conflictResolutionStrategyId)

                        throws SmartTicketFacadeException {

    if (synchronizing != null) {

      return synchronizing.synchronizeMovieRatings(movieRatings,

                    conflictResolutionStrategyId);

    }

    throw new SmartTicketFacadeException("User not logged in.");

  }



  // ... ...

}

5.4 Implementation Techniques

The MVC and facade patterns define the overall architecture of the application. In addition, Smart Ticket showcases some important behavioral patterns and implementation techniques that could greatly improve developer productivity.

5.4.1 Chain of Handlers

On the J2ME device side, the RemoteModelProxy class (see Listing 5.3) further delegates the action to a chain of handler classes that transparently work out the dirty plumbing of the RMS and HTTP serialization. The chained handlers are based on the RequestHandler interface and the RemoteModelRequestHandler abstract class (Listing 5.7), which implements the former:

Listing 5.7. The RemoteModelRequestHandler class
public interface RequestHandler {



    RequestHandler getNextHandler();

    void init() throws ApplicationException;

    void destroy() throws ApplicationException;

}



abstract public class RemoteModelRequestHandler

           implements RequestHandler, RemoteModel {



  private RemoteModelRequestHandler nextHandler;

  private Preferences preferences;

  protected static ProgressObserver progressObserver;



  public RemoteModelRequestHandler(

      RemoteModelRequestHandler nextHandler) {

    this.nextHandler = nextHandler;

  }



  public RequestHandler getNextHandler() {

    return nextHandler;

  }



  public void init() throws ApplicationException {

    if (nextHandler != null) {

      nextHandler.init();

    }

    return;

  }



  public void destroy() throws ApplicationException {

    if (nextHandler != null) {

      nextHandler.destroy();

    }

    return;

  }



  public void login(String userName, String password)

            throws ModelException, ApplicationException {

    getRemoteModelRequestHandler().login(userName, password);

    return;

  }



  public void createAccount(AccountInfo accountInfo)

            throws ModelException, ApplicationException {

    getRemoteModelRequestHandler().createAccount(accountInfo);

    return;

  }



  // Other action methods declared in RemoteModel

  // ... ...

}

Concrete handler classes extend the RemoteModelRequestHandler class. A chain of handlers is established through nested constructors. Two handler classes are available in the Smart Ticket application: the RMSCacheHandler and HTTPCommunicationHandler classes. Listing 5.8 illustrates how the chain is assembled and used (e.g., getMovie()) in the RemoteModelProxy class.

Listing 5.8. Assemble the handler chain in class RemoteModelProxy
public class RemoteModelProxy extends ModelObjectLoader

                                 implements RemoteModel {



  private RemoteModelRequestHandler requestHandlerChain;

  private Preferences preferences = null;

  private Hashtable movies = new Hashtable();



  public RemoteModelProxy(String serviceURL)

                 throws ApplicationException {

    requestHandlerChain =

        new RMSCacheHandler(

            new HTTPCommunicationHandler(null, serviceURL));

    return;

  }



  // ... ...



  // get a movie from the chain of handlers

  public Movie getMovie(String movieKey)

            throws ModelException, ApplicationException {

    Movie movie = (Movie) movies.get(movieKey);



    if (movie == null) {

      movie = requestHandlerChain.getMovie(movieKey);

      movies.put(movieKey, movie);

    }

    return movie;

  }

  // Other action methods etc.



}

A handler can selectively implement any action methods in the RemoteModel interface. There are two possibilities:

  • If a RemoteModelProxy class calls an action method not implemented by the first handler class in the chain, the default implementation in the base class RemoteModelRequestHandler ensures that the call is passed to the next handler in the chain.

  • If a handler in a chain decides that it has finished processing an action, it returns directly. Otherwise, it can invoke the same action method in the base class to pass it to the next handler in the chain.

The following code snippets (Listing 5.9 and 5.10) illustrate how to implement the getMovie() method in the two handlers. The RMSCacheHandler looks up the on-device cache for the requested movie. If the requested movie is not cached, RMSCacheHandler calls its base class's getMovie() method, which passes the control to the next handler in the chain: the HTTPCommunicationHandler class. The getMovie() method in HTTPCommunicationHandler performs some network tasks to retrieve the movie object from the J2EE back end. To understand the inner workings of the HTTPCommunicationHandler class, you need to read on to the next section.

Listing 5.9. The getMovie() method in RMSCacheHandler
public class RMSCacheHandler extends RemoteModelRequestHandler {



  // ... ...



  public Movie getMovie(String movieKey)

            throws ModelException, ApplicationException {

    IndexEntry indexEntry = rmsAdapter.getIndexEntry(movieKey,

                IndexEntry.TYPE_MOVIE, IndexEntry.MODE_ANY);



    if (indexEntry != null) {

      return rmsAdapter.loadMovie(indexEntry.getRecordId());

    }

    return super.getMovie(movieKey);

  }



  // ... ...

}
Listing 5.10. The getMovie() method in HTTPCommunicationHandler
public class HTTPCommunicationHandler

            extends RemoteModelRequestHandler {

  // ... ...



  public Movie getMovie(String movieKey)

          throws ModelException, ApplicationException {

    HttpConnection connection = null;

    DataOutputStream outputStream = null;

    DataInputStream inputStream = null;



    try {

      connection = openConnection();

      updateProgress();



      outputStream = openConnectionOutputStream(connection);

      outputStream.writeByte(MessageConstants.OPERATION_GET_MOVIE);

      outputStream.writeUTF(movieKey);

      outputStream.close();

      updateProgress();



      inputStream = openConnectionInputStream(connection);

      Movie movie = Movie.deserialize(inputStream);

      updateProgress();

      return movie;

    } catch (IOException ioe) {

      throw new

         ApplicationException(ErrorMessageCodes.ERROR_CANNOT_CONNECT);

    }

    finally {

      closeConnection(connection, outputStream, inputStream);

    }

  }



  // ... ...

}

5.4.2 Binary RPC over HTTP

In the model layer, the HTTPCommunicationHandler class in the RemoteModelProxy class invokes remote procedures on the J2EE server side through a binary RPC protocol over the HTTP.

All RPC requests from the client to the server follow the same basic pattern: The first byte in the HTTP request data stream specifies the action method to be executed on the serverside session facade EJB. The RPC request code constants are defined in the MessageConstants class (Listing 5.11).

Listing 5.11. The RPC action codes in MessageConstants
package com.sun.j2me.blueprints.smartticket.shared.midp;



  public final class MessageConstants {

  public static final byte OPERATION_LOGIN_USER = 0;

  public static final byte OPERATION_CREATE_ACCOUNT = 1;

  public static final byte OPERATION_UPDATE_ACCOUNT = 2;

  public static final byte OPERATION_GET_THEATERS = 3;

  public static final byte OPERATION_GET_THEATER_SCHEDULE = 4;

  public static final byte OPERATION_GET_MOVIE = 5;

  public static final byte OPERATION_GET_MOVIE_POSTER = 6;

  public static final byte OPERATION_GET_MOVIE_SHOWTIMES = 7;

  public static final byte OPERATION_GET_SEATING_PLAN = 8;

  public static final byte OPERATION_RESERVE_SEATS = 9;

  public static final byte OPERATION_PURCHASE_TICKETS = 10;

  public static final byte OPERATION_CANCEL_SEAT_RESERVATION = 11;

  public static final byte OPERATION_GET_LOCALES = 12;

  public static final byte OPERATION_GET_RESOURCE_BUNDLE = 13;

  public static final byte OPERATION_INITIATE_SYNCHRONIZATION = 14;

  public static final byte OPERATION_SYNCHRONIZE_MOVIE_RATINGS = 15;

  public static final byte OPERATION_COMMIT_MOVIE_RATINGS = 16;

  public static final byte ERROR_NONE = 0;

  public static final byte ERROR_UNKNOWN_OPERATION = 1;

  public static final byte ERROR_SERVER_ERROR = 2;

  public static final byte ERROR_MODEL_EXCEPTION = 3;

  public static final byte ERROR_REQUEST_FORMAT = 4;



  private MessageConstants() {}

}

The second byte to the end of the request stream encodes a sequence of UTF strings that represent the parameters to be passed to the remote method. The response HTTP stream contains the RPC return value. The format is unique to each method, and you have to look at the source code for each method to figure out the exact format. The two code snippets below demonstrate the entire RPC round trip to get a list of theaters using a zip code. The RPC request is assembled in HTTPCommunicationHandler's action method getTheaters() (Listing 5.12), and the response array is unmarshaled by the shared model object Theater (Listing 5.13).

Listing 5.12. The HTTPCommunicationHandler class generates the RPC request in the handler chain
package com.sun.j2me.blueprints.smartticket.client.midp.model;



public class HTTPCommunicationHandler

             extends RemoteModelRequestHandler {

  // ... ...



 public Theater[] getTheaters(String zipCode)

            throws ModelException, ApplicationException {

    HttpConnection connection = null;

    DataOutputStream outputStream = null;

    DataInputStream inputStream = null;



    try {

      connection = openConnection();

      updateProgress();

      outputStream = openConnectionOutputStream(connection);



      outputStream.writeByte(MessageConstants.OPERATION_GET_THEATERS);

      outputStream.writeUTF(zipCode);

      outputStream.close();

      updateProgress();



      inputStream = openConnectionInputStream(connection);



      // The first number in the response stream indicates

      // the number of theater objects to follow.

      Theater[] theaters = new Theater[inputStream.readInt()];



      // Iterate to unmarshal all theater objects in the response.

      for (int i = 0; i < theaters.length; i++) {

        theaters[i] = Theater.deserialize(inputStream);

      }

      updateProgress();

      return theaters;

    } catch (IOException ioe) {

      throw new

        ApplicationException(ErrorMessageCodes.ERROR_CANNOT_CONNECT);

    } finally {

        closeConnection(connection, outputStream, inputStream);

    }

  }



  // Other action methods

}

Listing 5.13. The Theater class in the J2ME model layer unmarshals the RPC response
package com.sun.j2me.blueprints.smartticket.shared.midp.model;



public class Theater {

  private String primaryKey;

  private String name;

  private String address;

  private String zipCode;



  // ... ...



  public static Theater deserialize(DataInputStream dataStream)

                                     throws ApplicationException {

    try {

      Theater theater = new Theater();

      theater.zipCode = dataStream.readUTF();

      theater.primaryKey = dataStream.readUTF();

      theater.name = dataStream.readUTF();

      theater.address = dataStream.readUTF();

      return theater;

    } catch (IOException ioe) {

      throw new ApplicationException(ioe);

    }

  }

  // ... ...

}

The SmartTicketServlet first determines the RPC action code from the first byte in the request stream. It then dispatches the RPC to the corresponding action method through the facade and passes all the RPC parameters remaining in the stream. In the Smart Ticket application, the client and server are tightly coupled. This approach can improve network efficiency, since each RPC exchange can be specially designed and optimized. However, the trade-off is development speed and robustness. If we make small changes to the server, the protocol and the parsing code on the client are likely to need to change too. We need to keep track of and update the code in multiple places, which could prove error prone. We also often need to recompile and redistribute clients.

5.4.3 The Clientside Thread Model

The Smart Ticket application uses a sophisticated threading model on the mobile client side. During a prolonged background task, another thread displays a moving gauge to the user indicating the progress (Figure 5.6). The gauge screen could also provide a button for the user to cancel the long action if she does not want to wait.

Figure 5.6. The progress gauge.
 
 

As we have seen, action methods in the UIController class are simply wrappers of the runWithProgress() method (Listing 5.14), which sets the display to ProgressObserverUI and starts the EventDispatcher thread. The ProgressObserverUI screen displays a gauge and an optional Stop button, which is monitored by the main MIDlet system UI thread. As we described in Section 5.3.1, the EventDispatcher thread eventually delegates the action to methods in the model layer. The model action method calls the ProgressObserverUI's updateProgress() method (Listing 5.15) at certain stages over the execution to update the gauge and inform the user of the progress (see Listing 5.12).

Listing 5.14. The runWithProgress() method in the UIController class
public class UIController {



  // Action methods ...



  public void chooseMovieRequested() {

    runWithProgress(

      new EventDispatcher(

        EventIds.EVENT_ID_CHOOSEMOVIEREQUESTED, mainMenuUI),

        getString(UIConstants.PROCESSING), false);

  }



  // Action methods ...



  public void runWithProgress(Thread thread, String title,

                                boolean stoppable) {

    progressObserverUI.init(title, stoppable);

    getDisplay().setCurrent(progressObserverUI);

    thread.start();

  }



  class EventDispatcher extends Thread {

    // ... ...



    public void run() {

      // Switch -- case statements to delegate

      // actions to the model layer

    }

  }

}

Listing 5.15. The ProgressObserverUI class
public class ProgressObserverUI extends Form

     implements ProgressObserver, CommandListener {

  private UIController uiController;

  private static final int GAUGE_MAX = 8;

  private static final int GAUGE_LEVELS = 4;

  int current = 0;

  Gauge gauge;

  Command stopCommand;

  boolean stoppable;

  boolean stopped;



  public ProgressObserverUI(UIController uiController) {

      super("");

      gauge = new Gauge("", false, GAUGE_MAX, 0);

      stopCommand =

         new Command(uiController.getString(UIConstants.STOP),

                                Command.STOP, 10);

      append(gauge);

      setCommandListener(this);

  }



  public void init(String note, boolean stoppable) {

    gauge.setValue(0);

    setNote(note);

    setStoppable(stoppable);

    stopped = false;

  }



  public void setNote(String note) {

    setTitle(note);

  }



  public boolean isStoppable() {

    return stoppable;

  }



  public void setStoppable(boolean stoppable) {

    this.stoppable = stoppable;

    if (stoppable) {

      addCommand(stopCommand);

    } else {

      removeCommand(stopCommand);

    }

  }



  // Indicates whether the user has stopped the progress.

  // This message should be called before calling update.

  public boolean isStopped() {

    return stopped;

  }

  public void updateProgress() {

    current = (current + 1) % GAUGE_LEVELS;

    gauge.setValue(current * GAUGE_MAX / GAUGE_LEVELS);

  }



  public void commandAction(Command c, Displayable d) {

    if (c == stopCommand) {

      stopped = true;

    }

  }

}
Last modified on Wednesday, 23 September 2009 20:53
Vicky

Vicky

E-mail: This e-mail address is being protected from spambots. You need JavaScript enabled to view it