Enterprise J2ME Developing Mobile Java Applications [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Enterprise J2ME Developing Mobile Java Applications [Electronic resources] - نسخه متنی

Michael Juntao Yuan

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید



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 (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;
}
}
}


/ 204