package org.msh.tb.webservices; import org.jboss.seam.Component; import org.jboss.seam.faces.FacesMessages; import org.jboss.seam.international.StatusMessage; import org.jboss.seam.security.Identity; import org.msh.tb.application.AppFacesMessages; import org.msh.tb.application.TransactionManager; import org.msh.tb.login.AuthenticatorBean; import javax.persistence.EntityManager; import javax.xml.bind.ValidationException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.List; /** * Support for remote procedure calls (web services), handling authentication, transaction, error and preparation of response to the client. * This class wraps common operations when dealing with remote calls from Web Services. This is an abstract class and its main usage is * being declared as an anonymous class implementing the execute(); method. The execute() method must implement * the action to be performed, and its return value is serialized (to XML) and sent back to the client as a {@link Response} object. *

* The action wrapped by {@link RemoteActionHandler} is fired by calling the run(); method. Inside the run() method, * the class will try to authenticate the client (if the sessionID) was provided, start a transaction (if the getTransaction() is true) * and call the execute() method. It also catches any exception thrown and wrap them inside the {@link Response} object * sent back to the client. *

* If the sessionId is not provided (by the constructor or the setSessionId() method), the authentication will not be done, * and it's the responsibility of the action to authenticate or answer the client without authentication. * * @author Ricardo Memoria * */ public abstract class RemoteActionHandler { private String sessionId; private Object data; private Response response; private TransactionManager transaction; private boolean transactional= true; /** * Constructor of the class passing the client session identification as parameter. If the * sessionId is provided, the class will try to authenticate the client before executing * the action * @param sessionId */ public RemoteActionHandler(String sessionId) { super(); this.sessionId = sessionId; } public RemoteActionHandler(String sessionId, Object data) { super(); this.sessionId = sessionId; this.data = data; } /** * Default constructor of the class */ public RemoteActionHandler() { super(); } /** * Abstract method that should be implemented to execute the action of the remote call. This method * is never called directly, but by the run() method. *

* @return The result of the action. This result will be serialized to XML using the {@link XmlSerializer} class * and make available as a response to the client by the return of the method run() */ protected abstract Object execute(Object data) throws ValidationException; /** * Get the result type used as generic type * @return Class of E */ public Class getResultType() { Type[] types = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments(); if (types.length == 0) { return null; } return (Class) ((ParameterizedType) getClass() .getGenericSuperclass()).getActualTypeArguments()[0]; } /** * Create instance of the response class * @return */ protected Response createResponseInstance() { try { Class clazz = getResultType(); if (clazz != null) { return clazz.newInstance(); } } catch (Exception e) { // do nothing, because there is no response class // throw new RuntimeException(e); } return new Response(); } /** * Return the response object that will be serialized to the caller * @return instance of the Response class */ public E getResponse() { if (response == null) { response = createResponseInstance(); } return (E)response; } /** * Run the remote call handler, calling the execute() method. If information about user * authentication is supported, the system will call the authenticate method before. * @return instance of the {@link Response} class already serialized to the client */ public E run() { response = getResponse(); try { // authenticate first ? if (sessionId != null) { if (!authenticate()) return (E)response; } if (transactional) beginTransaction(); // run the action Object result = execute(data); if (transactional) commitTransaction(); if (result != null) response.setResult( ObjectSerializer.serializeToXml(result) ); } catch (ValidationException ve) { if (transactional) rollbackTransaction(); setResponseError(Response.RESP_VALIDATION_ERROR, ve.getMessage()); } catch (Exception e) { e.printStackTrace(); if (transactional) rollbackTransaction(); setResponseError(Response.RESP_UNEXPECTED_ERROR, e.toString()); } return (E)response; // return getSerializedResponse(); } /** * Check if there are messages declared in the {@link FacesMessages} SEAM component. If there * is no message, the method returns false. If there are messages, the system returns * true and fills the response with the proper validation error messages * @return */ protected boolean checkValidationErrors() { AppFacesMessages fm = (AppFacesMessages)FacesMessages.instance(); List msgs = fm.getStatusMessages(); if (msgs.size() == 0) return false; StringBuilder s = new StringBuilder(); for (StatusMessage msg: msgs) { if (s.length() > 0) s.append(", "); msg.interpolate(); s.append(msg.getSummary()); } setResponseError(Response.RESP_VALIDATION_ERROR, s.toString()); return true; } /** * Set the error number and error msg of the response * @param errorno * @param errormsg */ public void setResponseError(int errorno, String errormsg) { response.setErrorno(errorno); response.setErrormsg(errormsg); } /** * Create a serialized XML of the response property * @return */ protected String getSerializedResponse() { return ObjectSerializer.serializeToXml(response); } /** * Authenticate the remote call using the session identification * @return */ protected boolean authenticate() { AuthenticatorBean authenticator = (AuthenticatorBean)Component.getInstance("authenticator"); authenticator.setSessionId(sessionId); // instantiate credentials, otherwise the authenticator method will not be called Identity.instance().getCredentials(); Identity.instance().login(); if (!Identity.instance().isLoggedIn()) { response.setErrorno(Response.RESP_INVALID_SESSION); return false; } FacesMessages.instance().clear(); return true; } /** * @return the sessionId */ public String getSessionId() { return sessionId; } /** * @param xmldata * @param clazz * @return */ public T deserializeFromXml(String xmldata, Class clazz) { return ObjectSerializer.deserializeFromXml(xmldata, clazz); } /** * Start a new transaction */ public void beginTransaction() { getTransaction().begin(); } /** * Commit a transaction that is under progress */ public void commitTransaction() { getTransaction().commit(); } /** * Roll back a transaction that is under progress */ public void rollbackTransaction() { getTransaction().rollback(); } /** * Return the transaction in use by the task * @return */ protected TransactionManager getTransaction() { if (transaction == null) transaction = (TransactionManager)Component.getInstance("transactionManager"); return transaction; } /** * Return the {@link EntityManager} instance in use * @return */ public EntityManager getEntityManager() { return transaction.getEntityManager(); } /** * @return the transactional */ public boolean isTransactional() { return transactional; } /** * @param transactional the transactional to set */ public void setTransactional(boolean transactional) { this.transactional = transactional; } }