lunes, 11 de junio de 2012

Sencha GXT3 RequestFactory Guice JPA

I am now starting a new blogspot. Next I will describe a Basic CRUD example with GXT Grid.
The project was build with Maven and is available via GIT https://github.com/poseidonjm/basic-crud
If you don't have Egit you can install Help -> Install New Software...

http://download.eclipse.org/egit/updates



I am using Eclipse Java EE IDE for Web Developers.
You should install Maven http://maven.apache.org/download.html download apache-maven-3.0.4-bin.zip

Install maven plugin Help -> Install New Software...
 m2e-wtp - http://download.jboss.org/jbosstools/updates/m2eclipse-wtp

Final Project

Database Table
I am using postgreSQL 8.4

create table colaboradores(
id bigserial primary key,
nombres varchar(250),
apellidos varchar(250),
edad int,
version int
);
General Structure
Project Structure
General POM.xml
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<!-- POM file generated with GWT webAppCreator -->
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>crud-example</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>GWT Maven Archetype</name>
<properties>
<!-- Convenience property to set the GWT version -->
<gwtVersion>2.4.0</gwtVersion>
<!-- GWT needs at least java 1.5 -->
<webappDirectory>${project.build.directory}/${project.build.finalName}</webappDirectory>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>sencha-gpl-release</id>
<name>Open source licensed Sencha artifacts</name>
<url>https://maven.sencha.com/repo/gpl-release/</url>
</repository>
</repositories>
<dependencies>
<!-- Google Web Toolkit (GWT) -->
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-user</artifactId>
<version>${gwtVersion}</version>
<scope>provided</scope>
</dependency>
<!-- For the servlet filter -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- RequestFactory server -->
<dependency>
<groupId>com.google.web.bindery</groupId>
<artifactId>requestfactory-server</artifactId>
<version>2.4.0</version>
</dependency>
<!-- RequestFactory will use JSR 303 javax.validation if you let it -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.0.0.GA</version>
<classifier>sources</classifier>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.2.0.Final</version>
<exclusions>
<exclusion>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Required by Hibernate validator because slf4j-log4j is
optional in the hibernate-validator POM -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.1</version>
</dependency>
<!-- sencha gxt3 -->
<dependency>
<groupId>com.sencha.gxt</groupId>
<artifactId>gxt</artifactId>
<version>3.0.0b</version>
</dependency>
<!-- sencha uibinder support -->
<dependency>
<groupId>com.sencha.gxt</groupId>
<artifactId>uibinder-bridge</artifactId>
<version>2.4.0</version>
</dependency>
<!-- google guice -->
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>3.0</version>
</dependency>
<!-- google guice-servlet -->
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-servlet</artifactId>
<version>3.0</version>
</dependency>
<!-- google guice-persist -->
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-persist</artifactId>
<version>3.0</version>
</dependency>
<!-- google-gin -->
<dependency>
<groupId>com.google.gwt.inject</groupId>
<artifactId>gin</artifactId>
<version>1.5.0</version>
<exclusions>
<exclusion>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-servlet</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- hibernate core -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>3.6.10.Final</version>
</dependency>
<!-- hibernate jpa -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.6.10.Final</version>
</dependency>
<!-- hibernate metamodel -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>1.2.0.Final</version>
</dependency>
<!-- postgresql jdbc connector -->
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.1-901-1.jdbc4</version>
</dependency>
</dependencies>
<build>
<!-- Generate compiled stuff in the folder used for developing mode -->
<outputDirectory>${webappDirectory}/WEB-INF/classes</outputDirectory>
<plugins>
<!-- requestfactory-apt runs an annotation processor (APT) to
instrument its service interfaces so that
RequestFactoryServer can decode client requests. Normally
you would just have a dependency on requestfactory-apt
with <scope>provided</scope>, but that won't work in
eclipse due to m2e bug
https://bugs.eclipse.org/bugs/show_bug.cgi?id=335036 -->
<plugin>
<groupId>org.bsc.maven</groupId>
<artifactId>maven-processor-plugin</artifactId>
<version>2.0.5</version>
<executions>
<execution>
<id>process</id>
<goals>
<goal>process</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<outputDirectory>${project.build.directory}/generated-sources/gwt</outputDirectory>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.google.web.bindery</groupId>
<artifactId>requestfactory-apt</artifactId>
<version>${gwtVersion}</version>
</dependency>
</dependencies>
</plugin>
<!-- Google Plugin for Eclipse (GPE) won't see the source
generated above by requestfactory-apt unless it is exposed
as an additional source dir-->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources/gwt</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<!-- GWT Maven Plugin -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<version>2.4.0</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test</goal>
<goal>i18n</goal>
<goal>generateAsync</goal>
</goals>
</execution>
</executions>
<!-- Plugin configuration. There are many available options, see
gwt-maven-plugin documentation at codehaus.org -->
<configuration>
<runTarget>example.html</runTarget>
<hostedWebapp>${webappDirectory}</hostedWebapp>
<i18nMessagesBundle>com.example.client.Messages</i18nMessagesBundle>
</configuration>
</plugin>
<!-- Copy static web files before executing gwt:run -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.1.1</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>exploded</goal>
</goals>
</execution>
</executions>
<configuration>
<webappDirectory>${webappDirectory}</webappDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.codehaus.mojo</groupId>
<artifactId>
gwt-maven-plugin
</artifactId>
<versionRange>
[2.4.0,)
</versionRange>
<goals>
<goal>generateAsync</goal>
<goal>i18n</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.bsc.maven</groupId>
<artifactId>
maven-processor-plugin
</artifactId>
<versionRange>
[2.0.5,)
</versionRange>
<goals>
<goal>process</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
view raw pom.xml hosted with ❤ by GitHub
GWT Module
<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='example'>
<inherits name='com.google.gwt.user.User' />
<inherits name='com.google.gwt.i18n.I18N'/>
<inherits name='com.google.web.bindery.requestfactory.RequestFactory' />
<inherits name='com.sencha.gxt.ui.GXT'/>
<inherits name="com.sencha.gwt.uibinder.UiBinder" />
<inherits name="com.google.gwt.inject.Inject"/>
<entry-point class='com.example.client.example' />
<extend-property name="locale" values="es_PE"/>
<!-- Specify the paths for translatable code -->
<source path='client' />
<source path='shared' />
</module>
view raw example.gwt.xml hosted with ❤ by GitHub
Here I am include modules like RequestFactory,  GXT, UIBinder, GIN and set my locale to es_PE (spanish-Perú).

EntryPoint
package com.example.client;
import com.example.client.gin.ClientGinjector;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.RootPanel;
public class example implements EntryPoint {
private final ClientGinjector injector = GWT.create(ClientGinjector.class);
public void onModuleLoad() {
RootPanel.get().add(injector.getColaboradorPanel());
}
}
view raw example.java hosted with ❤ by GitHub
So where is the code? Simple I am using Gin and is like Guice for GWT projects. It means you can use the powerful Dependency Intection in your GWT code.

What is Dependency Intection?
In a nutshell "Never use new use @Inject instead". Guice will create your object and initialized injected fields.

In GIN I need a Ginjector and create this with GWT.create().
private final ClientGinjector injector = GWT.create(ClientGinjector.class);

Ginjector code
package com.example.client.gin;
import com.example.client.components.ColaboradorPanel;
import com.google.gwt.inject.client.GinModules;
import com.google.gwt.inject.client.Ginjector;
@GinModules(ClientModule.class)
public interface ClientGinjector extends Ginjector {
ColaboradorPanel getColaboradorPanel();
}
Here I get a ColaboradorPanel to attach to the RootPanel. A Ginjector need a Module.

Client Module
The next code was extracted from http://cleancodematters.com/2011/05/19/gwt-requestfactory_with_gin/
package com.example.client.gin;
import com.example.client.Messages;
import com.example.client.images.AppImages;
import com.example.shared.service.AppRequestFactory;
import com.google.gwt.core.client.GWT;
import com.google.gwt.inject.client.AbstractGinModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.web.bindery.event.shared.EventBus;
import com.google.web.bindery.event.shared.SimpleEventBus;
public class ClientModule extends AbstractGinModule {
@Override
protected void configure() {
bind(AppImages.class).in(Singleton.class);
bind(Messages.class).in(Singleton.class);
bind(EventBus.class).to(SimpleEventBus.class).in(Singleton.class);
}
@Provides
@Singleton
public AppRequestFactory createRequestFactory(EventBus eventBus) {
AppRequestFactory factory = GWT.create(AppRequestFactory.class);
factory.initialize(eventBus);
return factory;
}
}
Here I configure how GIN will create objects. My Messages Interface should be a Singleton, my ClientBundle AppImages too. In RequestFactory is needed an EventBus here I am using a SimpleEventBus and is a Singleton. There is a Provider method createRequestFactory that create my AppRequestFactory Interface and initialized it with a SimpleEventBus. More about GIN http://code.google.com/p/google-gin/.

ColaboradorPanel
package com.example.client.components;
import java.util.ArrayList;
import java.util.List;
import com.example.client.Messages;
import com.example.client.events.SaveEvent;
import com.example.client.events.SaveEvent.SaveHandler;
import com.example.shared.proxy.ColaboradorProxy;
import com.example.shared.service.AppRequestFactory;
import com.example.shared.service.ColaboradorService;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.web.bindery.event.shared.EventBus;
import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver;
import com.google.web.bindery.requestfactory.shared.Receiver;
import com.google.web.bindery.requestfactory.shared.RequestContext;
import com.sencha.gxt.core.client.IdentityValueProvider;
import com.sencha.gxt.core.client.Style.SelectionMode;
import com.sencha.gxt.core.client.ValueProvider;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.ModelKeyProvider;
import com.sencha.gxt.data.shared.PropertyAccess;
import com.sencha.gxt.data.shared.SortInfo;
import com.sencha.gxt.data.shared.loader.FilterConfig;
import com.sencha.gxt.data.shared.loader.FilterPagingLoadConfig;
import com.sencha.gxt.data.shared.loader.FilterPagingLoadConfigBean;
import com.sencha.gxt.data.shared.loader.LoadResultListStoreBinding;
import com.sencha.gxt.data.shared.loader.PagingLoadResult;
import com.sencha.gxt.data.shared.loader.PagingLoader;
import com.sencha.gxt.data.shared.loader.RequestFactoryProxy;
import com.sencha.gxt.widget.core.client.Dialog;
import com.sencha.gxt.widget.core.client.box.ConfirmMessageBox;
import com.sencha.gxt.widget.core.client.button.TextButton;
import com.sencha.gxt.widget.core.client.event.HideEvent;
import com.sencha.gxt.widget.core.client.event.HideEvent.HideHandler;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.form.NumberPropertyEditor;
import com.sencha.gxt.widget.core.client.grid.CheckBoxSelectionModel;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.grid.filters.GridFilters;
import com.sencha.gxt.widget.core.client.grid.filters.NumericFilter;
import com.sencha.gxt.widget.core.client.grid.filters.StringFilter;
import com.sencha.gxt.widget.core.client.info.Info;
import com.sencha.gxt.widget.core.client.selection.SelectionChangedEvent;
import com.sencha.gxt.widget.core.client.selection.SelectionChangedEvent.SelectionChangedHandler;
import com.sencha.gxt.widget.core.client.toolbar.PagingToolBar;
public class ColaboradorPanel implements IsWidget {
interface ColaboradorProxyProperties extends PropertyAccess<ColaboradorProxy> {
ModelKeyProvider<ColaboradorProxy> id();
ValueProvider<ColaboradorProxy, String> nombres();
ValueProvider<ColaboradorProxy, String> apellidos();
ValueProvider<ColaboradorProxy, Integer> edad();
}
public interface Binder extends UiBinder<Widget, ColaboradorPanel> {
}
public interface Driver extends RequestFactoryEditorDriver<ColaboradorProxy, ColaboradorEditor>{
}
private final SaveHandler saveHandler = new SaveHandler() {
public void onSave(SaveEvent event) {
RequestContext context = driver.flush();
if(!driver.hasErrors()){
editor.hide();
context.fire();
}
}
};
@Inject Messages messages;
private ColaboradorProxy colaborador;
private ListStore<ColaboradorProxy> store;
private PagingLoader<FilterPagingLoadConfig, PagingLoadResult<ColaboradorProxy>> loader;
private final Driver driver;
private final ColaboradorEditor editor;
private final AppRequestFactory factory;
private final Binder uiBinder;
@UiField(provided = true)
Grid<ColaboradorProxy> grid;
@UiField(provided = true)
PagingToolBar toolBar;
@UiField
TextButton edit;
@UiField
TextButton delete;
@Inject
public ColaboradorPanel(final Binder uiBinder, EventBus eventBus, Provider<AppRequestFactory> provider, Driver driver, ColaboradorEditor editor){
this.uiBinder = uiBinder;
this.editor = editor;
this.driver = driver;
this.factory = provider.get();
driver.initialize(factory, editor);
eventBus.addHandler(SaveEvent.getType(), saveHandler);
}
public Widget asWidget() {
RequestFactoryProxy<FilterPagingLoadConfig, PagingLoadResult<ColaboradorProxy>> proxy = new RequestFactoryProxy<FilterPagingLoadConfig, PagingLoadResult<ColaboradorProxy>>() {
@Override
public void load(FilterPagingLoadConfig loadConfig,
Receiver<? super PagingLoadResult<ColaboradorProxy>> receiver) {
ColaboradorService cs = factory.colaboradorService();
List<SortInfo> sortInfo = createRequestSortInfo(cs, loadConfig.getSortInfo());
List<FilterConfig> filterConfig = createRequestFilterConfig(cs, loadConfig.getFilters());
cs.list(loadConfig.getOffset(), loadConfig.getLimit(), sortInfo, filterConfig).to(receiver);
cs.fire();
}
};
loader = new PagingLoader<FilterPagingLoadConfig, PagingLoadResult<ColaboradorProxy>>(proxy){
@Override
protected FilterPagingLoadConfig newLoadConfig() {
return new FilterPagingLoadConfigBean();
}
};
loader.setRemoteSort(true);
ColaboradorProxyProperties props = GWT.create(ColaboradorProxyProperties.class);
store = new ListStore<ColaboradorProxy>(props.id());
loader.addLoadHandler(new LoadResultListStoreBinding<FilterPagingLoadConfig, ColaboradorProxy, PagingLoadResult<ColaboradorProxy>>(store));
toolBar = new PagingToolBar(2);
toolBar.bind(loader);
toolBar.getElement().getStyle().setProperty("borderBottom", "none");
IdentityValueProvider<ColaboradorProxy> identity = new IdentityValueProvider<ColaboradorProxy>();
final CheckBoxSelectionModel<ColaboradorProxy> sm = new CheckBoxSelectionModel<ColaboradorProxy>(identity);
sm.setSelectionMode(SelectionMode.MULTI);
ColumnConfig<ColaboradorProxy, String> nombresColumn = new ColumnConfig<ColaboradorProxy, String>(props.nombres(), 150, messages.firstName());
ColumnConfig<ColaboradorProxy, String> apellidosColumn = new ColumnConfig<ColaboradorProxy, String>(props.apellidos(), 150, messages.lastName());
ColumnConfig<ColaboradorProxy, Integer> edadColumn = new ColumnConfig<ColaboradorProxy, Integer>(props.edad(), 80, messages.age());
List<ColumnConfig<ColaboradorProxy, ?>> l = new ArrayList<ColumnConfig<ColaboradorProxy, ?>>();
l.add(sm.getColumn());
l.add(nombresColumn);
l.add(apellidosColumn);
l.add(edadColumn);
ColumnModel<ColaboradorProxy> cm = new ColumnModel<ColaboradorProxy>(l);
grid = new Grid<ColaboradorProxy>(store, cm) {
@Override
protected void onAfterFirstAttach() {
super.onAfterFirstAttach();
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
public void execute() {
loader.load();
}
});
}
};
grid.setLoader(loader);
grid.setSelectionModel(sm);
grid.getView().setStripeRows(true);
GridFilters<ColaboradorProxy> filters = new GridFilters<ColaboradorProxy>(loader);
filters.initPlugin(grid);
filters.setLocal(false);
filters.addFilter(new StringFilter<ColaboradorProxy>(props.nombres()));
filters.addFilter(new StringFilter<ColaboradorProxy>(props.apellidos()));
filters.addFilter(new NumericFilter<ColaboradorProxy, Integer>(props.edad(), new NumberPropertyEditor.IntegerPropertyEditor()));
grid.getSelectionModel().addSelectionChangedHandler(new SelectionChangedHandler<ColaboradorProxy>() {
public void onSelectionChanged(
SelectionChangedEvent<ColaboradorProxy> event) {
int size = event.getSelection().size();
if(size == 0){
edit.setEnabled(false);
delete.setEnabled(false);
}else if(size == 1){
edit.setEnabled(true);
delete.setEnabled(true);
}else if(size > 1){
edit.setEnabled(false);
delete.setEnabled(true);
}
}
});
return uiBinder.createAndBindUi(this);
}
@UiHandler("add")
public void onAdd(SelectEvent event){
ColaboradorService cs = factory.colaboradorService();
colaborador = cs.create(ColaboradorProxy.class);
cs.persist(colaborador).to(new Receiver<ColaboradorProxy>() {
@Override
public void onSuccess(ColaboradorProxy response) {
Info.display(messages.titlePanel(), messages.saveSuccessful());
loader.load();
}
});
driver.edit(colaborador, cs);
editor.clearFields();
editor.show(messages.addColaborador());
}
@UiHandler("edit")
public void onEdit(SelectEvent event){
ColaboradorService cs = factory.colaboradorService();
colaborador = grid.getSelectionModel().getSelectedItem();
cs.persist(colaborador);
driver.edit(colaborador, cs);
editor.clearFields();
editor.show(messages.editColaborador());
}
@UiHandler("delete")
public void onDelete(SelectEvent event){
List<ColaboradorProxy> colaboradores = grid.getSelectionModel().getSelectedItems();
String mensaje;
if(colaboradores.size() == 1)
mensaje = messages.deleteConfirm(colaboradores.get(0).getNombres());
else
mensaje = messages.deleteAllConfirm();
ConfirmMessageBox box = new ConfirmMessageBox(messages.titlePanel(), mensaje);
box.addHideHandler(new HideHandler() {
public void onHide(HideEvent event) {
Dialog btn = (Dialog) event.getSource();
if(!"No".equals(btn.getHideButton().getText())){
ColaboradorService cs = factory.colaboradorService();
List<ColaboradorProxy> colaboradores = grid.getSelectionModel().getSelectedItems();
cs.remove(colaboradores).fire(new Receiver<Void>() {
@Override
public void onSuccess(Void response) {
Info.display(messages.titlePanel(), messages.deleteSuccessful());
loader.load();
}
});
}
}
});
box.show();
}
}
In GWT there is not Java Reflection and GXT need access to the properties. For that purpose here is the interface ColaboradorProxyProperties.

I created a SaveEvent to be fired when a user click on Save Button.

SaveEvent
package com.example.client.events;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.HasHandlers;
public class SaveEvent extends GwtEvent<SaveEvent.SaveHandler> {
public static Type<SaveHandler> TYPE = new Type<SaveHandler>();
public interface SaveHandler extends EventHandler {
void onSave(SaveEvent event);
}
public SaveEvent() {
}
@Override
protected void dispatch(SaveHandler handler) {
handler.onSave(this);
}
@Override
public Type<SaveHandler> getAssociatedType() {
return TYPE;
}
public static Type<SaveHandler> getType() {
return TYPE;
}
public static void fire(HasHandlers source) {
source.fireEvent(new SaveEvent());
}
}
view raw SaveEvent.java hosted with ❤ by GitHub
Inside there is already created a SaveHandler.
I created this class using a GWTP Plugin for Eclipse http://code.google.com/p/gwt-platform/wiki/EclipsePlugin Once installed you can use this wizard to easily create your events.

I entered the EventName: SaveEvent and check the Has Handlers box.

The Editor is the Dialog where the user entered your information and click on Save button. Here the SaveEvent is fired and the method save of SaveHandler is executed. Here I call the flush method to update the object modified in the editor. An next if the driver has not eny errors I hide the Editor and fire the context RequestContext.

What Is a RequestContext
In RequestFactory all changes in an object are done inside a context. When you want to persist your changes call the fire methot of a context. Your changes are sent to the server and are saved to a database. It means you can make many changes before sent your changes to the server.

Next I Inject Messages interface using Field Injection.
@Inject Messages messages;

Then I use Constructor Injection to initialized required variables.

@Inject
public ColaboradorPanel(final Binder uiBinder, EventBus eventBus, Provider<AppRequestFactory> provider, Driver driver, ColaboradorEditor editor)

What happens first Constructor Injection or Field Injection?
The answer is Constructor Injection. It means you can't use messages variable inside the constructor because you will get a NullPointerException. If you need a variable inside a constructor inject it using Constructor Injection.

For create a GXT Grid you need create a proxy, loader and a store. Inside the proxy I use two helper function createRequestSortInfo createRequestFilterConfig to get sort info and filter info. This is needed because in Request Factory you cannot sent any object to the server. You only can sent interfaces that extends ValueProxy or EntityProxy. You sent a EntityProxy if there is an Entity Implementation on the server  It means if you have a table in a database. For other objects use ValueProxy.

For create objects and edit In RequestFactory with GWT Editor. I use one method persist()

Create an Object with GWT Editor
First I declare a field member
private ColaboradorProxy colaborador;

If I wanna create an object I create en empty Object
colaborador = cs.create(ColaboradorProxy.class);

Then bind the object with the Editor
driver.edit(colaborador, cs);

Next show the Editor
editor.show(messages.addColaborador());

When the user see the Editor or Dialog will see empty TextFields because is binded with an empty object.
Then the user fill the Form and click on Save. The SaveEvent is fired and The SaveHandler is executed. Here I still have an empty object I you do a System.out.println(colaborador.getAge()) you will get nothing then I call the method flush() on driver and the empty object is update with the editor information the user was entered next you can do System.out.println(colaborador.getAge()) an will get what you want.
Next I fire the context and sent my changes to server.

Edit an Object with GWT Editor
Get the object selected
colaborador = grid.getSelectionModel().getSelectedItem();

And repeat the above steps.
When the user see the editor will see the information selected and can do changes and save them because It is binded with an selected object and is not empty. Here the same happens if you don't call the method flush() on driver your object won't be updated.

Two way for creating UiBinder
When you use UiBinder you should have two files ColaboradorPanel.java and ColaboradorPanel.ui.xml
Your java file may be writed in two ways:
First way
public class ColaboradorPanel extends Composite {
public interface Binder extends UiBinder<Widget, ColaboradorPanel> {
}
private static Binder uiBinder = GWT.create(Binder.class);
public ColaboradorPanel() {
initWidget(uiBinder.createAndBindUi(this));
}
}

If you want to extend from Window you can see http://www.sencha.com/forum/showthread.php?181348-(beta2)-Editor-extends-Window-doesn-t-work
Second way
public class ColaboradorPanel implements IsWidget {
public interface Binder extends UiBinder<Widget, ColaboradorPanel> {
}
private static Binder uiBinder = GWT.create(Binder.class);
public ColaboradorPanel(){
}
public Widget asWidget() {
return uiBinder.createAndBindUi(this);
}
}

ColaboradorPanel.ui.xml
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui"
xmlns:gxt="urn:import:com.sencha.gxt.widget.core.client"
xmlns:container="urn:import:com.sencha.gxt.widget.core.client.container"
xmlns:toolbar="urn:import:com.sencha.gxt.widget.core.client.toolbar"
xmlns:button="urn:import:com.sencha.gxt.widget.core.client.button"
xmlns:grid="urn:import:com.sencha.gxt.widget.core.client.grid" >
<ui:with type="com.example.client.images.AppImages" field="images" />
<ui:with type="com.example.client.Messages" field="messages" />
<ui:with type="com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData" field="verticalLayoutData">
<ui:attributes width="1" height="-1" />
</ui:with>
<ui:with type="com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData" field="centerLayoutData">
<ui:attributes width="1" height="1" />
</ui:with>
<ui:style>
.background {
background-color: white;
}
</ui:style>
<g:VerticalPanel spacing="10">
<gxt:FramedPanel ui:field="panel" collapsible="true" headingText="{messages.titlePanel}" pixelSize="550, 300">
<container:VerticalLayoutContainer borders="true" addStyleNames="{style.background}">
<container:child layoutData="{verticalLayoutData}">
<toolbar:ToolBar>
<button:TextButton text="{messages.add}" ui:field="add" icon="{images.add}" />
<toolbar:SeparatorToolItem />
<button:TextButton text="{messages.edit}" ui:field="edit" icon="{images.update}" enabled="false" />
<toolbar:SeparatorToolItem />
<button:TextButton text="{messages.delete}" ui:field="delete" icon="{images.delete}" enabled="false" />
<toolbar:FillToolItem />
</toolbar:ToolBar>
</container:child>
<container:child layoutData="{centerLayoutData}" >
<grid:Grid ui:field="grid" />
</container:child>
<container:child layoutData="{verticalLayoutData}">
<toolbar:PagingToolBar ui:field="toolBar" />
</container:child>
</container:VerticalLayoutContainer>
</gxt:FramedPanel>
</g:VerticalPanel>
</ui:UiBinder>

ColaboradorEditor.java
All fields that need binding should be declared like class fields. If the widget is only in UiBinder and not declared like class field the binding with GWT Editor does not work.
In UiBinder all widgets are create by GWT. If you need pass an argument by UiBinder you can use
@UiFactory http://blog.jeffdouglas.com/2010/02/24/gwt-uibinder-passing-objects-to-widgets/
In another way you can pass the argument in java code and your field should be annotated with @UiField(provided=true) what means the object will be created by java code and not by UiBinder.
For add events I use @UiHandler("save") annotation where "save" identifies the button Save.

ColaboradorEditor.ui.xml
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui"
xmlns:gxt="urn:import:com.sencha.gxt.widget.core.client"
xmlns:form="urn:import:com.sencha.gxt.widget.core.client.form"
xmlns:container="urn:import:com.sencha.gxt.widget.core.client.container"
xmlns:button="urn:import:com.sencha.gxt.widget.core.client.button" >
<ui:with type="com.example.client.images.AppImages" field="images" />
<ui:with type="com.example.client.Messages" field="messages" />
<ui:with type="com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData" field="verticalLayoutData">
<ui:attributes width="1" height="-1" />
</ui:with>
<ui:style>
.important {
font-weight: bold;
}
</ui:style>
<gxt:Window buttonAlign="END" resizable="false" >
<gxt:FramedPanel ui:field="form" headerVisible="false" >
<form:FieldSet ui:field="fieldSet" headingText="{messages.infoUser}" collapsible="false" >
<container:VerticalLayoutContainer>
<container:child layoutData="{verticalLayoutData}">
<form:FieldLabel text="{messages.firstName}">
<form:widget>
<form:TextField ui:field="nombres" allowBlank="false" />
</form:widget>
</form:FieldLabel>
</container:child>
<container:child layoutData="{verticalLayoutData}">
<form:FieldLabel text="{messages.lastName}">
<form:widget>
<form:TextField ui:field="apellidos" allowBlank="false" />
</form:widget>
</form:FieldLabel>
</container:child>
<container:child layoutData="{verticalLayoutData}">
<form:FieldLabel text="{messages.age}">
<form:widget>
<form:NumberField ui:field="edad" allowDecimals="false" allowNegative="false" />
</form:widget>
</form:FieldLabel>
</container:child>
</container:VerticalLayoutContainer>
</form:FieldSet>
</gxt:FramedPanel>
<gxt:button>
<button:TextButton text="{messages.cancel}" ui:field="cancel" icon="{images.cancel}" />
</gxt:button>
<gxt:button>
<button:TextButton text="{messages.save}" ui:field="save" icon="{images.save}" />
</gxt:button>
</gxt:Window>
</ui:UiBinder>
AppImages.java
package com.example.client.images;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;
public interface AppImages extends ClientBundle {
@Source("add.gif")
ImageResource add();
@Source("delete.gif")
ImageResource delete();
@Source("update.png")
ImageResource update();
@Source("save.png")
ImageResource save();
@Source("cancel.png")
ImageResource cancel();
}
view raw AppImages.java hosted with ❤ by GitHub
I am using a ClientBundle for application images and this class is injected by GIN where is needed and is not necessary use GWT.create.

EntityBase.java
package com.example.server.domain;
import java.io.Serializable;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.PrePersist;
/**
* EntityBase, base entity for all entities.
*/
@MappedSuperclass
public abstract class EntityBase implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
protected Long id;
private Integer version = 0;
/**
* Auto-increment version # whenever persisted
*/
@PrePersist
void onPersist()
{
this.version++;
}
public Long getId() {
return id;
}
public Integer getVersion() {
return version;
}
}
view raw EntityBase.java hosted with ❤ by GitHub
There is not database table associated with this class. RequestFactory need a two fields in every domain class "id" and "version" these are columns in database table too.

Colaborador.java
package com.example.server.domain;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
* The persistent class for the colaboradores database table.
*
*/
@Entity
@Table(name="colaboradores")
public class Colaborador extends EntityBase {
private static final long serialVersionUID = 1L;
private String apellidos;
private Integer edad;
private String nombres;
public Colaborador() {
}
public String getApellidos() {
return this.apellidos;
}
public void setApellidos(String apellidos) {
this.apellidos = apellidos;
}
public Integer getEdad() {
return this.edad;
}
public void setEdad(Integer edad) {
this.edad = edad;
}
public String getNombres() {
return this.nombres;
}
public void setNombres(String nombres) {
this.nombres = nombres;
}
}
Here I have my entity class. In eclipse I can use a wizard to generate this entity class.
First you should enable JPA Facet in your eclipse project.

You need configure a data source Window -->Show View --> Data Source Explorer
and create a new connection


Right click on your package and select  New --> JPA Entities from tables


RequestFactory with Guice
RequestFactory is not integrate with Guice por default. In that case there is a helpful set of classes that make RequestFactory injectable https://github.com/etiennep/injected-requestfactory. I only did copy and paste of that classes.

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>com.example.server.MyGuiceServletConfig</listener-class>
</listener>
<!-- Default page to serve -->
<welcome-file-list>
<welcome-file>example.html</welcome-file>
</welcome-file-list>
</web-app>
view raw web.xml hosted with ❤ by GitHub
Here I configure GuiceFilter. And GuiceServletContextListener.

MyGuiceServletConfig.java

package com.example.server;
import com.example.server.requestfactory.InjectedRequestFactoryModule;
import com.example.server.requestfactory.InjectedRequestFactoryServlet;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.persist.PersistFilter;
import com.google.inject.persist.jpa.JpaPersistModule;
import com.google.inject.servlet.GuiceServletContextListener;
import com.google.inject.servlet.ServletModule;
public class MyGuiceServletConfig extends GuiceServletContextListener {
@Override
protected Injector getInjector() {
return Guice.createInjector(
new ServletModule(){
@Override
protected void configureServlets() {
install(new JpaPersistModule("myFirstJpaUnit")); // like we saw earlier.
filter("/*").through(PersistFilter.class);
install(new InjectedRequestFactoryModule());
serve("/gwtRequest").with(InjectedRequestFactoryServlet.class);
//filter("/*").through(MyFilter.class);
//filter("*.css").through(MyCssFilter.class);
// etc..
//serve("*.html").with(MyServlet.class);
//serve("/my/*").with(MyServlet.class);
// etc..
}
});
}
}

In Guice you can configure all servlets and filters in java code here I configure my InjectedRequestFactoryServlet not use web.xml anymore. More info http://code.google.com/p/google-guice/wiki/ServletModule.

persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="myFirstJpaUnit" transaction-type="RESOURCE_LOCAL" >
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<!-- JPA entities must be registered here -->
<class>com.example.server.domain.Colaborador</class>
<class>com.example.server.domain.EntityBase</class>
<properties>
<property name="hibernate.connection.driver_class" value="org.postgresql.Driver" />
<property name="hibernate.connection.url" value="jdbc:postgresql://localhost:5432/example" />
<property name="hibernate.connection.username" value="postgres" />
<property name="hibernate.connection.password" value="admin" />
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
<property name="hibernate.id.new_generator_mappings" value="true" />
</properties>
</persistence-unit>
</persistence>
view raw persistence.xml hosted with ❤ by GitHub
Here I configured my connections parameters.

ColaboradorDao.java
package com.example.server.dao;
import java.util.List;
import javax.persistence.EntityManager;
import com.example.server.domain.Colaborador;
import com.example.server.resultbean.ColaboradorPagingLoadResultBean;
import com.example.server.util.Paginate;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.persist.Transactional;
import com.sencha.gxt.data.shared.SortInfoBean;
import com.sencha.gxt.data.shared.loader.FilterConfigBean;
public class ColaboradorDao implements GenericDao<Colaborador> {
@Inject Provider<EntityManager> emProvider;
@Inject Paginate<Colaborador> pag;
@Transactional
public Colaborador persist(Colaborador entity) {
emProvider.get().persist(entity);
return entity;
}
public ColaboradorPagingLoadResultBean list(int offset, int limit, List<SortInfoBean> sortInfo, List<FilterConfigBean> filterConfig) {
List<Colaborador> list = pag.paginate(Colaborador.class, offset, limit, sortInfo, filterConfig);
Long count = pag.count(Colaborador.class);
return new ColaboradorPagingLoadResultBean(list, count.intValue(), offset);
}
@Transactional
@Override
public void remove(List<Colaborador> entities) {
for(Colaborador entity: entities){
emProvider.get().remove(entity);
}
}
}
Here I use injection fields to inject the Entity Manager and an Util class Paginate. Entity manager is initialized by Guice http://code.google.com/p/google-guice/wiki/JPA .
In JPA all objects are not persisted before commit the transaction. In Guice there is a useful annotation @Transactional http://code.google.com/p/google-guice/wiki/Transactions that made the method transactional.

ColaboradorPagingLoadResultBean.java
package com.example.server.resultbean;
import java.util.List;
import com.example.server.domain.Colaborador;
import com.sencha.gxt.data.shared.loader.PagingLoadResultBean;
@SuppressWarnings("serial")
public class ColaboradorPagingLoadResultBean extends PagingLoadResultBean<Colaborador> {
public ColaboradorPagingLoadResultBean(List<Colaborador> list, int totalLength, int offset) {
super(list, totalLength, offset);
}
protected ColaboradorPagingLoadResultBean() {
super();
}
}
This class that extend PagingLoadResultBean is needed for objects that will be shown in a GXT Grid.

Paginate.java
package com.example.server.util;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import com.example.server.domain.EntityBase;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.sencha.gxt.data.shared.SortDir;
import com.sencha.gxt.data.shared.SortInfoBean;
import com.sencha.gxt.data.shared.loader.FilterConfigBean;
public class Paginate<T extends EntityBase> {
private Provider<EntityManager> emProvider;
private List<FilterConfigBean> filterConfig;
@Inject
public Paginate(Provider<EntityManager> emProvider){
this.emProvider = emProvider;
}
public List<T> paginate(Class<T> clazz, int offset, int limit, List<SortInfoBean> sortInfo, List<FilterConfigBean> filterConfig){
this.filterConfig = filterConfig;
CriteriaBuilder cb = emProvider.get().getCriteriaBuilder();
CriteriaQuery<T> c = cb.createQuery(clazz);
Root<T> r = c.from(clazz);
c.where(condition(cb, r).toArray(new Predicate[]{}));
if(sortInfo.size() == 0)
c.orderBy(cb.desc(r.get("id")));
else{
if(sortInfo.get(0).getSortDir() == SortDir.ASC)
c.orderBy(cb.asc(r.get(sortInfo.get(0).getSortField())));
if(sortInfo.get(0).getSortDir() == SortDir.DESC)
c.orderBy(cb.desc(r.get(sortInfo.get(0).getSortField())));
}
TypedQuery<T> q = emProvider.get().createQuery(c);
q.setFirstResult(offset);
q.setMaxResults(limit);
return q.getResultList();
}
public Long count(Class<T> clazz){
CriteriaBuilder cb = emProvider.get().getCriteriaBuilder();
CriteriaQuery<Long> c = cb.createQuery(Long.class);
Root<T> r = c.from(clazz);
c.where(condition(cb, r).toArray(new Predicate[]{}));
c.select(cb.count(r));
return emProvider.get().createQuery(c).getSingleResult();
}
private List<Predicate> condition(CriteriaBuilder cb, Root<T> r){
List<Predicate> predicates = new ArrayList<Predicate>();
for(FilterConfigBean s : filterConfig){
if("contains".equals(s.getComparison())){
predicates.add(cb.like(cb.lower(r.<String>get(s.getField())), "%"+s.getValue().toLowerCase()+"%"));
}
if("gt".equals(s.getComparison())){
predicates.add(cb.gt(r.<Double>get(s.getField()), Double.valueOf(s.getValue())));
}
if("lt".equals(s.getComparison())){
predicates.add(cb.lt(r.<Double>get(s.getField()), Double.valueOf(s.getValue())));
}
if("eq".equals(s.getComparison())){
predicates.add(cb.equal(r.<Double>get(s.getField()), Double.valueOf(s.getValue())));
}
if("on".equals(s.getComparison())){
}
if("after".equals(s.getComparison())){
}
if("before".equals(s.getComparison())){
}
}
return predicates;
}
}
view raw Paginate.java hosted with ❤ by GitHub
This class use CriteriaBuilder for paginate a database table. http://www.ibm.com/developerworks/java/library/j-typesafejpa/

EntityLocator.java
package com.example.server.locator;
import javax.persistence.EntityManager;
import com.example.server.domain.EntityBase;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.web.bindery.requestfactory.shared.Locator;
/**
* Generic @Locator for objects that extend EntityBase
*/
public class EntityLocator extends Locator<EntityBase, Long> {
@Inject Injector injector;
@Inject Provider<EntityManager> emProvider;
@Override
public EntityBase create(Class<? extends EntityBase> clazz) {
return injector.getInstance(clazz);
}
@Override
public EntityBase find(Class<? extends EntityBase> clazz, Long id) {
return emProvider.get().find(clazz, id);
}
/**
* it's never called
*/
@Override
public Class<EntityBase> getDomainType() {
throw new UnsupportedOperationException();
// or return null;
}
@Override
public Long getId(EntityBase domainObject) {
return domainObject.getId();
}
@Override
public Class<Long> getIdType() {
return Long.class;
}
@Override
public Object getVersion(EntityBase domainObject) {
return domainObject.getVersion();
}
}
In Request Factory you work with interfaces that are Proxy and require a locator. This is a generic locator for all Proxies.

ColaboradorProxy.java
package com.example.shared.proxy;
import com.example.server.domain.Colaborador;
import com.example.server.locator.EntityLocator;
import com.google.web.bindery.requestfactory.shared.EntityProxy;
import com.google.web.bindery.requestfactory.shared.ProxyFor;
@ProxyFor(value = Colaborador.class, locator = EntityLocator.class)
public interface ColaboradorProxy extends EntityProxy {
String getNombres();
void setNombres(String nombres);
String getApellidos();
void setApellidos(String apellidos);
Integer getEdad();
void setEdad(Integer edad);
Long getId();
}
This is the object that you use in your GWT code and is initialized using GWT.create sent to the server and Request Factory does the magic to translate this proxy in an entity object. This proxy extend EntityProxy because represent a domain class or a database table.

ColaboradorService.java
package com.example.shared.service;
import java.util.List;
import com.example.server.dao.ColaboradorDao;
import com.example.server.requestfactory.InjectingServiceLocator;
import com.example.server.resultbean.ColaboradorPagingLoadResultBean;
import com.example.shared.proxy.ColaboradorProxy;
import com.google.web.bindery.requestfactory.shared.ProxyFor;
import com.google.web.bindery.requestfactory.shared.Request;
import com.google.web.bindery.requestfactory.shared.RequestContext;
import com.google.web.bindery.requestfactory.shared.Service;
import com.google.web.bindery.requestfactory.shared.ValueProxy;
import com.sencha.gxt.data.shared.SortInfo;
import com.sencha.gxt.data.shared.loader.FilterConfig;
import com.sencha.gxt.data.shared.loader.PagingLoadResult;
@Service(value = ColaboradorDao.class, locator = InjectingServiceLocator.class)
public interface ColaboradorService extends RequestContext {
Request<ColaboradorProxy> persist(ColaboradorProxy colaborador);
Request<Void> remove(List<ColaboradorProxy> colaborador);
@ProxyFor(value = ColaboradorPagingLoadResultBean.class)
public interface ColaboradorPagingLoadResultProxy extends ValueProxy, PagingLoadResult<ColaboradorProxy> {
public List<ColaboradorProxy> getData();
}
Request<ColaboradorPagingLoadResultProxy> list(int offset, int limit, List<? extends SortInfo> sortInfo, List<? extends FilterConfig> filterConfig);
}
For each database table I create a Service or RequestContext. The proxy ColaboradorPagingLoadResultProxy is required for GXT.

AppRequestFactory.java
package com.example.shared.service;
import com.google.web.bindery.requestfactory.shared.RequestFactory;
public interface AppRequestFactory extends RequestFactory {
public ColaboradorService colaboradorService();
}
Here I configure all my services.

Internationalization with GWT
I use two properties files

Messages.properties
titlePanel = RequestFactory Grid Example
add = Add
edit = Edit
delete = Delete
save = Save
cancel = Cancel
addColaborador = Add Colaborador
editColaborador = Edit Colaborador
deleteColaborador = Delete Colaborador
infoUser = User Information
firstName = First Name
lastName = Last Name
age = Age
saveSuccessful = Saved successful
deleteSuccessful = Deleted successful
deleteConfirm = Are you sure to delete {0}?
deleteAllConfirm = Are you sure to delete selected records?

Messages_es_PE.properties
titlePanel = RequestFactory Grid Ejemplo
add = Agregar
edit = Editar
delete = Eliminar
save = Guardar
cancel = Cancelar
addColaborador = Nuevo Colaborador
editColaborador = Editar Colaborador
deleteColaborador = Eliminar Colaborador
infoUser = Información de Usuario
firstName = Nombres
lastName = Apellidos
age = Edad
saveSuccessful = Se guardo correctamente
deleteSuccessful = Se elimino correctamente
deleteConfirm = ¿Esta seguro que desea eliminar al colaborador {0}?
deleteAllConfirm = ¿Esta seguro que desea eliminar los registros seleccionados?
There is needed an aditional interface. In GWT that interface could be generated.
Go to project path and write the command mvn gwt:i18n

Is generated in target/generated-sources

You should add to build path right click on generated-sources/gwt folder  Build Path --> Use as Source Folder.
You can use command mvn gwt:run to launch the Hosted Mode.

11 comentarios:

  1. Buenas Noches, estoy comenzando con java y gxt,estoy tratando de correr el ejemplo pero tengo un error en eclipse que dice "the import com.example.client.Messages can not be resolved"
    me podrias orientar.
    Gracias

    ResponderEliminar
  2. Eso quiere decir que la interfaz Messages no esta en el build path.
    Messages es auto generado con maven. Debes ir al directorio del proyecto con la linea de comandos y escribir mvn gwt:i18n
    Luego usando eclipse le das click derecho al folder target/generated-sources/gwt del menu contextual seleccionas Build Path --> Use as Source Folder.
    Puedes ejecutar el proyecto con mvn gwt:run

    ResponderEliminar
  3. Buenas, he estado experimentando en particular con lo siquiente:

    - RequestFactory
    - GXT
    - Grid con Paginacion.

    Este ejemplo ha sido muy util. Estoy teniendo un ligero problema y es ya practicamente al final del proceso cuando la data final va a ser devuelta al cliente. En particular, estoy devolviendo un objeto en el servidor de tipo:

    public CardPagingLoadResultBean findAll() {
    ...
    return new CardPagingLoadResultBean(cards, cards.size(), 1);

    }

    Despues de investigar, RequestFactory esta esperando una lista y evidentemente este objeto no lo es, lo cual causa la excepcion que aparece debajo.

    Cualquier idea o ayuda en esta area seria muy agradecida.

    Alguien ha tenido el mismo problema o tiene idea de que se pueda hacer en este caso?

    Gracias.

    Aug 16, 2012 12:39:29 PM com.google.web.bindery.requestfactory.server.RequestFactoryServlet doPost
    SEVERE: Unexpected error
    java.lang.ClassCastException: interface java.util.List
    at java.lang.Class.asSubclass(Class.java:3018)
    at com.google.web.bindery.requestfactory.server.ResolverServiceLayer.resolveClientType(ResolverServiceLayer.java:69)
    at com.google.web.bindery.requestfactory.server.ServiceLayerDecorator.resolveClientType(ServiceLayerDecorator.java:142)
    at com.google.web.bindery.requestfactory.server.ServiceLayerDecorator.resolveClientType(ServiceLayerDecorator.java:142)
    at com.google.web.bindery.requestfactory.server.ServiceLayerDecorator.resolveClientType(ServiceLayerDecorator.java:142)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)

    ResponderEliminar
    Respuestas
    1. En GXT Grid se usa el objeto CardPagingLoadResultBean(cards, count, 0) para envolver la lista cards y agregarle informacion adicional como el total de registros count (no confundir con el tamaño de la lista) y el offset.Por ejemplo si quiero mostrar 30 cards por página y el total de cards es 100 entonces count = 100 y limit=30, offset = 0, 30, 60, etc.
      Si vas a paginar los cards el código sería el siguiente

      public CardPagingLoadResultBean list(int offset, int limit, List sortInfo, List filterConfig) {

      List list = pag.paginate(Card.class, offset, limit, sortInfo, filterConfig);
      Long count = pag.count(Card.class);
      return new CardPagingLoadResultBean(list, count.intValue(), offset);
      }

      y el service está esperando el objeto CardPagingLoadResultProxy
      @Service(value = CardDao.class, locator = InjectingServiceLocator.class)
      public interface CardService extends RequestContext {

      ...
      @ProxyFor(value = CardPagingLoadResultBean.class)
      public interface CardPagingLoadResultProxy extends ValueProxy, PagingLoadResult {
      public List getData();
      }

      Request list(int offset, int limit, List sortInfo, List filterConfig);
      }

      el método getData() lo usa GXT para obtener la lista de CardProxy.
      En RequestFactory el objeto entidad Card del servidor tiene su correspondiente objeto cliente CardProxy. Así en el servidor está el objeto CardPagingLoadResultBean y en el cliente es CardPagingLoadResultProxy.

      Eliminar
  4. This resource was extremely helpful, and the example project was an excellent starting point for me. I am now figuring out how to protect these requestfactory calls with authentication with something like guice method interception similar to what is done here:

    http://dzone.com/snippets/secure-your-gwt-app-easily

    Not sure if you, or anyone else reading this blog had had any experience with this.

    ResponderEliminar
  5. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  6. Apache shiro
    !-- apache shiro -->
    <dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.2.1</version>
    </dependency>
    <dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>1.2.1</version>
    </dependency>
    <dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-guice</artifactId>
    <version>1.2.1</version>
    </dependency>

    Guice and Shiro Aop
    https://gist.github.com/poseidonjm/5171827
    Shiro Web Module
    https://gist.github.com/poseidonjm/5171913
    Authentication Filter
    https://gist.github.com/poseidonjm/5171942
    Custom Realm for dinamic roles
    https://gist.github.com/poseidonjm/5171969
    Custom realm for static roles
    https://gist.github.com/poseidonjm/5171990
    Util Class
    https://gist.github.com/poseidonjm/5172009
    Reference
    http://shiro.apache.org/java-authorization-guide.html

    Annotate your methods
    @RequiresPermissions("ACTIONS:DELETE")
    @Transactional
    public void remove(Action entity) {

    }
    or
    @RequiresRoles("OPERARIO")
    or
    @RequiresAuthentication

    Greetings

    ResponderEliminar
  7. Ooh I read this too late. I don't have an authorization solution yet, so this is extremely helpful. I ended up spending a lot of time figuring just the authentication piece out. I think you will get good feedback if you do a blogpost about this.

    Went through some of the files you provided, maybe I didn't spend enough time - it doesn't really explain how you would handle the client with things like session timeouts etc. - the solution I have below deals specifically with session timeouts. Protecting request factory servlets gets me only partially there. I still need to know what to do when session times out, or when the client gets an access denied message.

    Here is what I ended up doing:
    http://pastebin.com/mQQMMyAk

    And then:

    http://pastebin.com/bmq7azW6

    And when login is successful:

    http://pastebin.com/JqwghAHh

    ResponderEliminar
  8. Here is a revised version of the DefaultRequestTransport subclass. The pending requests queue was not getting contstructed correctly.

    http://pastebin.com/ir0wFbLU

    ResponderEliminar
  9. I use very similar RequestTransport https://gist.github.com/poseidonjm/5275288 and use it for
    @Provides
    @Singleton
    public AppRequestFactory createRequestFactory(EventBus eventBus, MyDefaultRequestTransport requestTransport) {
    AppRequestFactory factory = GWT.create(AppRequestFactory.class);
    factory.initialize(eventBus, requestTransport);
    return factory;
    }

    I use a login.jsp for login form using Apache Shiro. When get a SC_UNAUTHORIZED response I reload the page Window.Location.reload(); and because the session is expired Shiro redirects to login.jsp

    ResponderEliminar
  10. I am able to run this application on eclipse. We use IntelliJ Idea, can you tell me why the application do not run. Intellij do not able to find new generated files.

    Imran

    ResponderEliminar