default-logo

Realizzare un Pet Store/parte terza

Posted On 25 Gen 2008
Comment: Off

tutorialjavaTutorial Java – Dopo aver fornito una visione d’insieme del PetStore, evidenziando come ogni sua parte sia stata progettata ed implementata tenendo conto del paradigma model-view-controller, ed aver analizzato più nel dettaglio sia il model che la view dell’applicazione, non resta che dare un’occhiata più da vicino all’ultima parte dell’applicazione: il controller.
Il controller
Il controller è indubbiamente la parte più importante e contemporaneamente la più complessa del Pet Store ed in generale di qualsiasi applicazione progettata secondo il modello MVC. Esso, infatti, costituisce il collante fra le varie parti dell’applicazione, processando le richieste effettuate dall’utente attraverso la view e modificando il model in conseguenza di tali richieste. Il controller del PetStore è, come già è stato accennato nella prima parte dell’articolo, a sua volta logicamente suddiviso in più sottoparti che verranno analizzate nel seguito.

 

fig11

 

 

Il request processor
Nel Pet Store ogni qualvolta l’utente richiede un’azione particolare, come ad esempio di aggiungere un prodotto al proprio carrello, viene invocato un URL terminante con il suffisso .do che è mappato sulla servlet com.sun.j2ee.blueprints.waf.controller.web.MainServlet.
Questa servlet non fa altro che passare la request HTTP così come gli arriva dal browser al metodo processRequest di un oggetto istanza della classe com.sun.j2ee.blueprints.waf.controller.web.RequestProcessor che è riportato nel seguito:

 

public void processRequest(HttpServletRequest request) throws HTMLActionException, EventException, ServletException {
Event ev = null;
String fullURL = request.getRequestURI();
// get the screen name
String selectedURL = null;
int lastPathSeparator = fullURL.lastIndexOf(“/”) + 1;
if (lastPathSeparator != -1) {
selectedURL = fullURL.substring(lastPathSeparator,
fullURL.length());
}
ServiceLocator sl = (ServiceLocator)request.getSession().
getAttribute(WebKeys.SERVICE_LOCATOR);
WebClientController wcc = sl.getWebClientController();
HTMLAction action = getAction(selectedURL);
if (action != null) {
action.setServletContext(context);
action.doStart(request);
ev = action.perform(request);
EventResponse eventResponse = null;
if (ev != null) eventResponse = wcc.handleEvent(ev);
action.doEnd(request, eventResponse);
}
}

 

 

fig2

 

 

 

Come prima cosa questo metodo fa il parsing dell’URL richiesto, in modo da ottenerne l’ultima parte. Continuando con l’esempio dell’aggiunta di un prodotto al carrello, a questo punto la variabile selectedURL conterrebbe la stringa “cart.do”. Successivamente viene prelevato dalla sessione un oggetto che implementa l’interfaccia WebClientController. Le funzionalità di questo oggetto verranno chiarite nel seguito. Quindi viene creato l’oggetto HTMLAction, che effettuerà la vera e propria processazione della request. Il metodo getAction() legge dal file web.xml il nome della classe che si occuperà di tale processazione in base all’URL richiesta e ne genera un’istanza mediante la reflection. Ad esempio, l’HTMLAction che si fa carico di processare le modifiche al carrello è la classe com.sun.j2ee.blueprints.petstore.controller.web.actions.CartHTMLAction. Ciascuna HTMLAction deve implementare i metodi: doStart() che ha il compito di inizializzare l’oggetto, perform() che processa la request e doEnd() che esegue ulteriori azioni conseguenti all’evento di risposta generato dal WebClientController. In particolare il metodo perform() traduce la request HTTP in un oggetto di tipo Event che poi il RequestProcessor passerà al WebClientController che ne effettuerà la vera e propria processazione ritornando un EventResponse. Questo tipo di architettura consente di massimizzare il disaccoppiamento tra la view e le altre parti dell’applicazione. In tal modo se si dovesse decidere di voler cambiare il tipo di view utilizzata, ad esempio sostituendo le pagine JSP con una GUI Swing, basterà cambiare il RequestProcessor e implementare delle classi Action che traducano le richieste dell’utente negli stessi Event che già sono prodotti dalle HTMLAction, lasciando invariato il model e le altri parti del controller. Per capire come viene effettuata la trasformazione da request HTTP ad evento gestibile dalle altri parti del controller può essere utile analizzare più nel dettaglio com’è stata implementata la classe CartHTMLAction. Questa classe non ha bisogno di particolari inizializzazioni per cui il metodo doStart() è stato lasciato vuoto. Il metodo perform() è invece implementato come segue:

 

public Event perform(HttpServletRequest request)
throws HTMLActionException {
// Extract attributes we will need
String actionType= (String)request.getParameter(“action”);
HttpSession session = request.getSession();
// get the shopping cart helper
CartEvent event = null;
if (actionType == null) return null;
if (actionType.equals(“purchase”)) {
String itemId = request.getParameter(“itemId”);
event = new CartEvent(CartEvent.ADD_ITEM, itemId);
} else if (actionType.equals(“remove”)) {
String itemId = request.getParameter(“itemId”);
event = new CartEvent(CartEvent.DELETE_ITEM, itemId);
} else if (actionType.equals(“update”)) {
Map quantities = new HashMap();
Map parameters = request.getParameterMap();
for (Iterator it = parameters.keySet().iterator();
it.hasNext(); ) {
String name = (String) it.next();
String value = ((String[]) parameters.get(name))[0];
final String ITEM_QTY = “itemQuantity_”;
if (name.startsWith(ITEM_QTY)) {
String itemID = name.substring(ITEM_QTY.length());
Integer quantity = null;
try {
quantity = new Integer(value);
} catch (NumberFormatException nfe) {
quantity = new Integer(0);
}
quantities.put(itemID, quantity);
}
}
event = CartEvent.createUpdateItemEvent(quantities);
}
return event;
}

 

Come si può notare questo metodo costruisce e ritorna un oggetto di classe CartEvent che ha lo scopo di mappare la request HTTP in un evento, indipendente dal tipo di protocollo usato. In particolare se l’utente ha richiesto di aggiungere o rimuovere un prodotto dal carrello verrà creato un CartEvent rispettivamente di tipo ADD_ITEM o DELETE_ITEM. Se invece il cliente ha richiesto di aggiornare le quantità dei prodotti già presenti nel carrello, sarà generato un CartEvent di tipo UPDATE_ITEM che conterrà una Map avente come chiavi gli identificativi dei prodotti presenti nel carrello e come valori le quantità di ciascun prodotto. Infine il metodo doEnd() è così definito:

 

public void doEnd(HttpServletRequest request, EventResponse eventResponse) {
if (request.getSession().getAttribute(PetstoreKeys.CART) == null) {
ShoppingCartWebHelper cart = new ShoppingCartWebHelper();
cart.init(request.getSession());
request.getSession().setAttribute(PetstoreKeys.CART, cart);
}
}

 

Questo metodo ha semplicemente il compito di mettere in sessione, se già non ce ne uno, un oggetto di classe ShoppingCartWebHelper che, come visto nell’articolo riguardante la view, è il bean che viene utilizzato nelle pagine JSP per visualizzare gli oggetti presenti nel carrello.

 

Il web controller
L’oggetto WebClientController riceve dal RequestProcessor la richiesta dell’utente opportunamente codificata in un oggetto di classe Event. In particolare il RequestProcessor invoca sul WebClientController il seguente metodo handleEvent():

 

public synchronized EventResponse handleEvent(Event ev) throws EventException {
return ccEjb.processEvent(ev);
}

 

L’oggetto ccEjb è un’istanza dello stateful session bean com.sun.j2ee.blueprints.waf.controller.ejb.EJBClientControllerEJB che ha il compito di mantenere lo stato del sistema. Il WebClientController è dunque essenzialmente un proxy che passa le richieste dall’utente dal web tier all’ejb tier ritornando al web tier un oggetto EventResponse che codifica la risposta dell’application server e che, come si è visto, verrà poi utilizzato dal request processor. Tutti i metodi che accedono all’ejb tier sono sincronizzati in modo che non arrivino più richieste contemporanee di modifica allo stato del sistema.

 

L’ejb controller e la StateMachine
L’EJBClientController è l’analogo lato ejb tier del WebClientController. Il suo metodo processEvent() è infatti implementato come segue:

public EventResponse processEvent(Event ev) throws EventException {
return (sm.processEvent(ev));
}

L’oggetto sm è un’istanza della classe com.sun.j2ee.blueprints.waf.controller.ejb. StateMachine che è il core della business logic di tutta l’applicazione, essendo responsabile di modificare lo stato del model in risposta alle richieste dell’utente. In particolare il suo metodo processEvent(), progettato in modo da implementare un command pattern, si presenta come segue:

public EventResponse processEvent(Event ev) throws EventException {
String eventName = ev.getEventName();
String actionName = null;
EventResponse response = null;
if (eventName != null) {
actionName = getActionName(eventName);
EJBAction action = null;
try {
if (actionMap.get(actionName) != null) {
action = (EJBAction)actionMap.get(actionName);
} else {
action=(EJBAction) Class.forName(actionName).newInstance();
actionMap.put(actionName, action);
}
} catch (Exception ex) {
System.err.println(“StateMachine: error loading ” + actionName + ” :” + ex);
}
if (action != null) {
action.init(this);
action.doStart();
response = action.perform(ev);
action.doEnd();
}
}
return response;
}

 

 

fig3

 

 

Gli oggetti che vengono invocati dalla StateMachine per modificare il model devono implementare l’interfaccia EJBAction. Tali oggetti vengono creati mediante reflection e memorizzati nella Map actionMap che fa il caching delle istanze di tali oggetti. Il loro nome viene recuperato tramite JNDI dal file petstore_waf_ejb.xml che mappa ciascun Event generato dal request processor con l’EJBAction, che dovrà processare tale evento. Ad esempio, nel caso l’utente avesse richiesto di modificare il proprio carrello, il request processor genera un Event di tipo CartEvent per il quale nel file petstore_waf_ejb.xml è presente la seguente entry:

 

<env-entry>
<env-entry-name>event/CartEvent</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>
com.sun.j2ee.blueprints.petstore.controller.ejb.actions.CartEJBAction
</env-entry-value>
</env-entry>

 

pertanto tale Event verrà processato dalla classe CartEJBAction. Una volta istanziata, o recuperata dalla cache, l’EJBAction specifica per il tipo di Event da processare la StateMachine e invoca in sequenza su tale classe i metodi: init() che passa all’EJBAction la reference alla StateMachine, doStart() che inizializza l’EJBAction, perform() che prende in input l’Event, lo decodifica, aggiorna il model coerentemente con tale Event, e ritorna un EventResponse che codifica il risultato di tale aggiornamento ed infine doEnd() che esegue le eventuali azioni necessarie alla conclusione della processazione dell’Event come il rilascio delle risorse utilizzate dall’EJBAction. Per completare l’analisi del controller può essere utile dare uno sguardo all’implementazione della classe CartEJBAction. In questo EJBAction i metodi doStart() e doEnd() sono vuoti, mentre il metodo perform() è così implementato:

 

public EventResponse perform(Event e) throws EventException {
CartEvent ce = (CartEvent)e;
ShoppingClientFacadeLocal scf = (ShoppingClientFacadeLocal)
machine.getAttribute(“shoppingClientFacade”);
ShoppingCartLocal cart = scf.getShoppingCart();
switch (ce.getActionType()) {
case CartEvent.ADD_ITEM:
cart.addItem(ce.getItemId());
break;
case CartEvent.DELETE_ITEM:
cart.deleteItem(ce.getItemId());
break;
case CartEvent.UPDATE_ITEMS:
Map items = ce.getItems();
Iterator it = null;
if (items != null) it = items.keySet().iterator();
while ((it != null) && it.hasNext()) {
String itemId = (String)it.next();
int quantity=((Integer)items.get(itemId)).intValue();
cart.updateItemQty(itemId, quantity);
}
break;
}
return null;
}

Questo metodo non fa altro che richiamare, in funzione del tipo di CartEvent ricevuto in input, i metodi per aggiornare opportunamente lo stateful session bean che, come si è visto nell’articolo che analizzava il model del Pet Store, mantiene tutte le informazioni relative al carrello dell’utente.

About the Author