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
General Structure
Project Structure
General POM.xml
GWT Module
Here I am include modules like RequestFactory, GXT, UIBinder, GIN and set my locale to es_PE (spanish-Perú).
EntryPoint
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
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/
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
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
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
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
ColaboradorPanel.ui.xml
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
AppImages.java
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
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
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
Here I configure GuiceFilter. And GuiceServletContextListener.
MyGuiceServletConfig.java
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
Here I configured my connections parameters.
ColaboradorDao.java
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
This class that extend PagingLoadResultBean is needed for objects that will be shown in a GXT Grid.
Paginate.java
This class use CriteriaBuilder for paginate a database table. http://www.ibm.com/developerworks/java/library/j-typesafejpa/
EntityLocator.java
In Request Factory you work with interfaces that are Proxy and require a locator. This is a generic locator for all Proxies.
ColaboradorProxy.java
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
For each database table I create a Service or RequestContext. The proxy ColaboradorPagingLoadResultProxy is required for GXT.
AppRequestFactory.java
Here I configure all my services.
Internationalization with GWT
I use two properties files
Messages.properties
Messages_es_PE.properties
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.
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"
ResponderEliminarme podrias orientar.
Gracias
Eso quiere decir que la interfaz Messages no esta en el build path.
ResponderEliminarMessages 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
Buenas, he estado experimentando en particular con lo siquiente:
ResponderEliminar- 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)
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.
EliminarSi 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.
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:
ResponderEliminarhttp://dzone.com/snippets/secure-your-gwt-app-easily
Not sure if you, or anyone else reading this blog had had any experience with this.
Este comentario ha sido eliminado por el autor.
ResponderEliminarApache shiro
ResponderEliminar!-- 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
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.
ResponderEliminarWent 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
Here is a revised version of the DefaultRequestTransport subclass. The pending requests queue was not getting contstructed correctly.
ResponderEliminarhttp://pastebin.com/ir0wFbLU
I use very similar RequestTransport https://gist.github.com/poseidonjm/5275288 and use it for
ResponderEliminar@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
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.
ResponderEliminarImran