lundi 11 février 2008

Seam : a way to handle server-side exceptions

In this article, we'll see how one can handle server-side exceptions using seam remoting.

Because Seam Remoting doesn't handle gracefully those exceptions (see http://jira.jboss.org/jira/browse/JBSEAM-633), we must manage those exceptions and be able to show the user a comprehensive error message.

For my current ExtJS + Seam project, I'm using the following pattern :

- a business SessionBean which do the actual business job (open a transaction, do the persistence work, etc)



package xxx.yyy.zzz;

import java.util.List;
import javax.ejb.Local;
import xxx.yyy.zzz.Chauffeur;

/**
* @author cvigouroux
*
*/

@Local
public interface HelloAction
{
/**
* @param nouveauPays
* @return Le pays mis à jour
*/

public Chauffeur modifierChauffeur(Chauffeur nouveauChauffeur);
}



package xxx.yyy.zzz;

import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.TransactionPropagationType;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.log.Log;

import xxx.yyy.zzz.Chauffeur;

/**
* @author cvigouroux
*
*/

@Stateless
@Name("faucon80_cartographie_helloAction")
@SuppressWarnings("unchecked")
public class HelloActionBean implements HelloAction
{
@Logger
private Log log;

@In
private EntityManager entityManager;

/*
* (non-Javadoc)
*
* @see com.elomobile.faucon80.cartographie.session.metier.HelloAction#modifierChauffeur(com.elomobile.faucon80.cartographie.entity.Chauffeur)
*/

@Transactional(TransactionPropagationType.REQUIRED)
public Chauffeur modifierChauffeur(Chauffeur nouveauChauffeur)
{
// mise à jour de la date
// nouveauChauffeur.setDtheumod(new Date());

// mise à jour de la base de données
entityManager.merge(nouveauChauffeur);
entityManager.flush();

return nouveauChauffeur;
}
}



- an adapter for that SessionBean (using adapter design pattern) in the form of another SessionBean whose job is to call the business and to handle the exceptions. The exception is then redirected to the client in the form of a message with a criticity (success, information, error). In order to ligthen the exception handling code within each service method, I use an interceptor for doing the job.


package xxx.yyy.zzz;

import java.io.InputStream;
import java.util.List;

import javax.ejb.Local;

import org.jboss.seam.annotations.remoting.WebRemote;

import xxx.yyy.zzz.Chauffeur;
import xxx.yyy.zzz.web.messages.BasicPopupMessage;
import xxx.yyy.zzz.web.paging.PagingQueryParam;
import xxx.yyy.zzz.web.paging.PagingResultList;
import xxx.yyy.zzz.web.paging.QueryParam;
import xxx.yyy.zzz.web.paging.ResultList;

/**
* @author cvigouroux
*
*/

@Local
public interface HelloWebAction
{
/**
* @param nouveauPays
* @return Le pays mis à jour
* @throws Exception
*/

@WebRemote
public BasicPopupMessage modifierChauffeur(Chauffeur nouveauChauffeur) throws Exception;

/**
* Méthode destroy...
*/

public void destroy();

}



package xxx.yyy.zzz.session.web;

import java.io.InputStream;
import java.rmi.RemoteException;
import java.util.List;
import java.util.Map;

import javax.ejb.Remove;
import javax.ejb.Stateful;
import javax.interceptor.Interceptors;
import javax.xml.rpc.ServiceException;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.*;
import org.jboss.seam.log.Log;

import xxx.yyy.zzz.Identity;
import xxx.yyy.zzz.Chauffeur;
import xxx.yyy.zzz.session.metier.HelloAction;
import xxx.yyy.zzz.web.messages.BasicPopupMessage;
import xxx.yyy.zzz.web.paging.PagingQueryParam;
import xxx.yyy.zzz.web.paging.PagingResultList;
import xxx.yyy.zzz.web.paging.QueryParam;
import xxx.yyy.zzz.web.paging.ResultList;

/**
* @author cvigouroux
*
*/

@Stateful
@Scope(ScopeType.SESSION)
@Name("faucon80_cartographie_helloWebAction")
public class HelloWebActionBean implements HelloWebAction
{
@Logger
private Log log;

@In(create = true, value = "helloAction")
private HelloAction helloAction;

@In
private Map<String, String> messages;

/*
* (non-Javadoc)
*
* @see com.alteca.seam.prototype.session.web.HelloWebAction#updatePays(com.alteca.seam.prototype.entity.Tpays)
*/

@Interceptors(InterceptorBasicPopupMessageMethod.class)
public BasicPopupMessage modifierChauffeur(Chauffeur nouveauChauffeur) throws Exception
{
Identity.instance().checkRestriction("#{org.jboss.seam.security.identity.hasPermission('helloAction')}");

Chauffeur chauffeurMaj = helloAction.modifierChauffeur(nouveauChauffeur);

return new BasicPopupMessage(BasicPopupMessage.Criticite.INFO, messages
.get("helloAction.modifierChauffeur.succes"), messages.get("bouton.ok"), chauffeurMaj);
}
}




package xxx.yyy.zzz.session.web;

import java.util.Map;

import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;

import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.log.Log;
import org.jboss.seam.security.AuthorizationException;

import xxx.yyy.zzz.web.messages.BasicPopupMessage;

@Name("interceptorBasicPopupMessageMethod")
public class InterceptorBasicPopupMessageMethod
{
@Logger
private static Log log;

@AroundInvoke
public Object interceptGenericExceptions(InvocationContext invocation) throws Exception
{
Object o = invocation.getParameters()[0];

Map<String, String> messages = org.jboss.seam.international.Messages.instance();

try
{
return invocation.proceed();
}
catch (AuthorizationException ae)
{
return new BasicPopupMessage(BasicPopupMessage.Criticite.ERROR, messages.get("erreur.nonAuthorise"), messages.get("bouton.ok"), o);
}
catch (Exception e)
{
log.error("", e);
return new BasicPopupMessage(BasicPopupMessage.Criticite.ERROR, messages.get("erreur.generique") + e.getMessage(), messages.get("bouton.ok"), o);
}
}
}



On the javascript side, I use a standard callback function which intercepts the returned message and shows a message box with the transmitted message string.


/**
*
* @param {Ext.data.Store} store The store to which belong the record.
* @param {Ext.data.Record} record The record to be updated server-side.
* @param {String} formName The name of the form to update with the server-side updated bean.
* @param {function} callbackFunction The function to call after handling.
*/

Ext.data.SeamRemotingRecordHandler = function(store, record, formName, callbackFunction)
{
this.callback = function(reponse)
{
if (reponse.criticite != 'ERROR')
{
Ext.getCmp(formName).getForm().updateRecord(record);

// get new data from server
record.data = reponse.data;
record.endEdit();

// tell the store to refresh
store.commitChanges();

// loads the new record in the form
Ext.getCmp(formName).getForm().loadRecord(record);
}

if (reponse.criticite != 'SUCCES')
{
// show an information or an error message
Ext.Msg.show(
{
minWidth: 200,
closable: false,
msg: reponse.message,
buttons:
{
ok: reponse.messageBouton
},
icon: Ext.MessageBox[reponse.criticite]
});
}

// call client's callback function
callbackFunction();
}
}


Example of use :


var handler = new Ext.data.SeamRemotingRecordHandler(store, recordAModifier, "tpays-form", function()
{
});

helloWebAction.modifierChauffeur(copieRecord.data, handler.callback);

Aucun commentaire: