The two interfaces of the service must be present in the client package. In this howto, the package name is org.objectweb.easybeans.demo.sudoku.web.client.service.
The following two interfaces must be defined:
The interface that will be implemented by the remote service
The asynchronous interface, which is always a void method. A
new parameter must be added on the method and the name of the
interface must have a suffix of
Async
.
The following is an example of a service that takes an argument and returns an object (A solver).
package org.objectweb.easybeans.demo.sudoku.web.client.service; import org.objectweb.easybeans.demo.sudoku.web.client.api.GridData; import org.objectweb.easybeans.demo.sudoku.web.client.api.ServiceException; import com.google.gwt.user.client.rpc.RemoteService; /** * Interface used to call the servlet facade and then EJB3. * @author Florent Benoit */ public interface ServletFacadeService extends RemoteService { /** * Solve a sudoku grid and send in return the solved grid. * @param gridData the grid to solve * @return the solved grid. * @throws ServiceException if solving fails. */ GridData solve(GridData gridData) throws ServiceException; /** * Checks if a sudoku grid is valid. * @param gridData the grid to check * @throws ServiceException if checking fails. */ void check(GridData gridData) throws ServiceException; /** * Generate a sudoku grid and send it in return. * @return the generated grid. * @throws ServiceException if solving fails. */ GridData generate() throws ServiceException; }
![]() |
Note |
---|---|
GridData must be a serializable object. However, this
serialization is not JDK serialization. This class must implement
the
The Exception (ServiceException) also implements the
|
The signature is almost the same, except for the following:
The return type is void.
An extra argument is added in the method signature: the
AsyncCallback
object.
The name of the class ends with the
Async
keyword.
package org.objectweb.easybeans.demo.sudoku.web.client.service; import org.objectweb.easybeans.demo.sudoku.web.client.api.GridData; import com.google.gwt.user.client.rpc.AsyncCallback; /** * Asynchronous interface. Same parameters of interface but all methods have an AsyncCallback parameter and are void !. * @author Florent Benoit */ public interface ServletFacadeServiceAsync { /** * Asynchronous call to the solve method. * @param gridData the grid to solve * @param callback the callback to use for this method */ void solve(GridData gridData, AsyncCallback callback); /** * Checks if a sudoku grid is valid. * @param gridData the grid to check * @param callback the callback to use for this method */ void check(GridData gridData, AsyncCallback callback); /** * Generates a sudoku grid and send it in return. * @param callback the callback to use for this method */ void generate(AsyncCallback callback); }
![]() |
Note |
---|---|
The ServiceException is not thrown by the solve() method of
the Async interface. The exceptions will be reported in the
|
This class implements the
IsSerializable
interface.
package org.objectweb.easybeans.demo.sudoku.web.client.api; import com.google.gwt.user.client.rpc.IsSerializable; /** * Represents the data of a sudoku grid. * It is a serializable object (gwt) used by remote service. * @author Florent Benoit */ public class GridData implements IsSerializable { ... }
This class implements the
IsSerializable
interface and stores the
message.
Additionally, an empty constructor is required for the serialization.
package org.objectweb.easybeans.demo.sudoku.web.client.api; import com.google.gwt.user.client.rpc.IsSerializable; /** * Exception thrown by the remote service. <br /> * Exception needs to be serializable (gwt) * @author Florent Benoit */ public class ServiceException extends Exception implements IsSerializable { /** * The message of the exception. */ private String msg; /** * Empty message. */ public ServiceException() { super(); } /** * Builds an exception with a given message. * @param msg the message of the exception. */ public ServiceException(final String msg) { super(msg); this.msg = msg; } /** * Gets the message of the exception. * @return the message of the exception. */ public String getMessage() { return this.msg; } }
The implementation of the service will run on the server side. Therefore, it need not be present in the client package (no JavaScript transformation). The class will be in the server package.
The service is provided as a servlet. Then, the class must
extend the
com.google.gwt.user.server.rpc.RemoteServiceServlet
class.
The Class will look like the following:
package org.objectweb.easybeans.demo.sudoku.web.server.service; import org.objectweb.easybeans.demo.sudoku.web.client.api.GridData; import org.objectweb.easybeans.demo.sudoku.web.client.api.ServiceException; import com.google.gwt.user.server.rpc.RemoteServiceServlet; /** * Implementation of the service that runs on the server side. <br /> * All is delegate to the EJB3 session facade. * @author Florent Benoit */ public class ServletFacadeServiceImpl extends RemoteServiceServlet implements ServletFacadeService { /** * Checks if a sudoku grid is valid. * @param gridData the grid to check * @throws ServiceException if checking fails. */ public void check(final GridData gridData) throws ServiceException { try { getFacade().check(gridData.getData()); } catch (Exception e) { throw new ServiceException(e.getMessage()); } } /** * Solve a sudoku grid and send in return the solved grid. * @param gridData the grid to solve * @return the solved grid. * @throws ServiceException if solving fails. */ public GridData solve(final GridData gridData) throws ServiceException { int[][] solvedData = null; try { solvedData = getFacade().solve(gridData.getData()); } catch (Exception e) { throw new ServiceException(e.getMessage()); } GridData solvedGridData = new GridData(); solvedGridData.setData(solvedData); return solvedGridData; } ... }
A sessionBean facade will be used for delegating all the requests.
The service will use a getFacade() method. InitialContext is built, then the facade is searched and the session bean is returned. The bean can be cached to avoid getting a new bean each time.
The following is the code for this method:
/** * Gets the session facade bean. * @return the session bean. * @throws Exception if facade is not retrieved */ private SudokuFacade getFacade() throws Exception { SudokuFacade sudokuFacade = null; Context initialContext = null; Hashtable<String, String> env = new Hashtable<String, String>(); // Use the smart factory env.put(Context.INITIAL_CONTEXT_FACTORY, SmartContextFactory.class.getName()); // Use the default Provider URL port env.put(Context.PROVIDER_URL, "smart://localhost:2503"); initialContext = new InitialContext(env); sudokuFacade = (SudokuFacade) initialContext.lookup("SudokuFacade"); return sudokuFacade; }
![]() |
Note |
---|---|
The JNDI name is very simple as the attribute mappedName was used on the @Stateless annotation of the EJB3. The InitialContext factory is the smart factory. This factory allow to download the classes when they are required. The PROVIDER_URL is the default URL of the Smart component. |
When an operation must be performed on the facade session bean, it is done in the following way:
try { ... = getFacade().methodName(); } catch (Exception e) { throw new ServiceException(e.getMessage()); }
The exception thrown is wrapped in the ServiceException. Only the message is kept and thrown to the client. The error can be printed to the error log to have a full trace on the server side.
The service must be added to an xml file in order to declare it.
In this howto, the file is named
Sudoku.gwt.xml
.
The servet element is added to this file along with the class implementing the service and the endpoint of the service (/facade is the following example).
<module> <!-- Inherit the core Web Toolkit stuff. --> <inherits name='com.google.gwt.user.User'/> <!-- Specify the app entry point class. --> <entry-point class='org.objectweb.easybeans.demo.sudoku.web.client.Sudoku'/> <servlet path='/facade' class='org.objectweb.easybeans.demo.sudoku.web.server.service.ServletFacadeServiceImpl'/> </module>
An endpoint must be created. The path of the endpoint will use the name of the module and then /facade (the same entry as in the gwt.xml file).
The endpoint will be something similar to "http://localhost:8080/org.objectweb.easybeans.demo/facade".
By using GWT.getModuleBaseURL(), the port and the host need not be known.
ServletFacadeServiceAsync servletFacadeServiceAsync = (ServletFacadeServiceAsync) GWT .create(ServletFacadeService.class); ServiceDefTarget endpoint = (ServiceDefTarget) servletFacadeServiceAsync; endpoint.setServiceEntryPoint(GWT.getModuleBaseURL() + "/facade");
Before invoking the service, a callback must be built. The asynchronous interface of the service is used. As this interface requires a callback, it must be constructed beforehand.
A callback provides two methods:
An onSuccess(final Object
result)
method if the call completes
successfully.
An onFailure(final Throwable
caught)
method if the call fails.
For the onSuccess method, the result parameter can be cast to GridData, which is the return type of the original method's interface.
final AsyncCallback solvedCallback = new AsyncCallback() { /** * Called when an asynchronous call completes successfully. It is * always safe to downcast the parameter (of type * <code>Object</code>) to the return type of the original method * for which this is a callback. */ public void onSuccess(final Object result) { GridData solvedGridData = (GridData) result; ... } /** * Called when an asynchronous call fails to complete normally. * @param caught the failure. */ public void onFailure(final Throwable caught) { ... } };