The UI part of the QualiPSo Factory is compiled using Maven (generating a WAR file) and is included in a global EAR file which can be compiled using Maven too (via the factory-assembly module). That packaging can be done thanks to the Servlet 3.0 specification (especially the web fragment feature) which has been included in JBoss 6.0.0.M2 recently. So, to integrate a new module inside the QualiPSo Factory, two Maven modules has to be developed (one for the server part and one for the UI part). The main topic of those guidelines is about the User Interface development.
The communication between UI modules and server side modules is done using RMI (Java Remote Method Invocation).
GWT is used to develop the UI views. All graphical modules are small web apps including servlet server side and javascript client side to allow gwt components to communicate with the server using ajax calls. The integration of the UI modules (servlet composition) can be done by using a web-fragment.xml file inside each module.
Factory UI modules, as EJB modules, include unit and functional tests. Functional tests are using Selenium and are located in an other module because they imply the deployment of the application.
In the following guidelines, the factory-ui-service-project module will be used as the main example.
By the Maven archetype feature, you will be able to get an empty project with the right architecture. So, in the shell console, go to the working directory where the source code will be stored and then type that command line:
mvn archetype:generate -DarchetypeCatalog=http://radis.loria.fr/maven2/repo/archetype-catalog.xml
The general architecture of the UI project has to be respected in order to allow the integration of your module inside the whole factory. The imported module is designed as presented in the table below (for our example, we still use the Project module) :
| +- factory-ui-service-project/ | Module root |
| +- src/main/java/ | Java sources root |
| l +- project/ | Module name root |
| l l +- Project.gwt.xml | Xml file defining the module |
| l l +- client/ | Client folder root |
| l l l +- iserver/ | Servlet communication (javascript to servlet) |
| l l l l +- ProjectServlet.java | Interface of the module servlet |
| l l l l +- ProjectServletException.java | Custom exception for servlet use |
| l l l +- model/ | Java version of UI objects transferred between servlet and client navigator |
| l l l l +- Project.java | Java object used in the views (lighter than the server one) |
| l l l +- presenter/ | Logical part of the module |
| l l l l +- ProjectPresenter.java | Entry point (manages all views, handles UI module events, declares views to other UI modules and receives views actions) |
| l l l +- view/ | View part (ie. forms, buttons, ...) |
| l l l l +- DisplayCreateEditView.java | Java side of a view |
| l l l l +- DisplayCreateEditView.ui.xml | "Gwt-xml" side of a view |
| l l l +- resources/ | Physical resources files (ie. css, jpg, gif, ...) |
| l l l l +- ProjectResources.java | Java interface for accessing physical resources |
| l l l l +- css/ | Css folder root |
| l l l l l +- Style.java | Java interface for accessing css styles (can be accessed from the "gwt-xml" files) |
| l l l l l +- style.css | Css style sheet (gwt custom tags can be used) |
| l l l l +- img/ | Img folder root |
| l l +- dev/ | Dev folder root |
| l l l +- ProjectDev.gwt.xml | Xml file defining the module (for dev mode only, cf. Debugging) |
| l l +- server/ | Server folder root |
| l l l +- ProjectServletImpl.java | Implementation of the module servlet |
| +- src/main/resources/ | Resources folder root |
| l +- META-INF/web-fragment.xml | Xml file defining the servlet mapping (for production mode) |
| +- src/main/webapp/ | Webapp folder root |
| l +- WEB-INF/ | Html content (for dev mode only) |
| l l +- web.xml | Xml file defining the servlet mapping (for dev mode only, cf. Debugging) |
| l +- index.html | Html file defining the view being debugged (for dev mode only, cf. Debugging) |
| +- target/generated-sources/gwt | Gwt generated sources folder root |
| l +- iserver/ProjectServletAsync.java | Asynchronous version of the servlet interface (generated by gwt) |
First of all, the module configuration has to be set (in Eclipse). To do so, right-click on the Module root and select "Properties".
In order to ensure the compliance with the OpenParts framework and so to ensure an auto-integration of your module, the following files have to be developed as detailed. For each file, you can find the rules to be respected and a code example.
The content of this file must be as below :
<module rename-to='project'>
[1] <inherits name='org.qualipso.factory.ui.shared.QGWT.QGWT' />
[2] <inherits name='org.qualipso.factory.ui.shared.OpenParts.OpenParts' />
[3] <inherits name='com.google.gwt.rpc.RPC' />
[4] <source path='client' />
[5] <entry-point class='org.qualipso.factory.ui.service.project.client.presenter.ProjectPresenter' />
</module>That xml file is used for declaring the servlet of the module. Thanks to Servlet 3.0, that war composition is now allowed. !! MORE EXPLANATIONS NEEDED!!
<web-fragment xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd"
version="3.0">
<name>FactoryUiServiceProject</name>
<servlet>
<display-name>ProjectServlet</display-name>
<servlet-name>ProjectServlet</servlet-name>
<servlet-class>org.qualipso.factory.ui.service.project.server.ProjectServletImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ProjectServlet</servlet-name>
<url-pattern>/project/project</url-pattern>
</servlet-mapping>
</web-fragment>That Java file is the interface where the servlet methods have to be defined. Mainly, create, display, update and delete methods are the ones to be declared.
public interface ProjectServlet extends RpcService {
public void createProject(String path, String name, String summary, String license) throws ProjectServletException;
(...)
}That Java file is the auto-generated asynchronous interface based on the ProjectServlet.java interface. It's generated by a GWT compilation (cf. compilation instructions to know how to compile). That allows asynchronous communication between client and server parts.
public interface ProjectServletAsync
{
void createProject( java.lang.String path, java.lang.String name, java.lang.String summary, java.lang.String license, AsyncCallback<Void> callback );
(...)
}That Java file is the implementation of the ProjectServlet.java interface. Here is the real code where UI part is calling Server part services [1]. It's also here where converters [2] between server objects and UI objects have to be done (cf. Project.java model object for the UI part).
@SuppressWarnings("serial")
public class ProjectServletImpl extends RpcServlet implements ProjectServlet {
private static Log logger = LogFactory.getLog(ProjectServletImpl.class);
private ProjectService projectService;
private BrowserService browserService;
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
try {
projectService = (ProjectService) Factory.findService(ProjectService.SERVICE_NAME);
browserService = (BrowserService) Factory.findService(BrowserService.SERVICE_NAME);
} catch (FactoryException e) {
logger.error("unable to get project service", e);
throw new ServletException(e);
}
}
@Override
public void createProject(String path, String name, String summary, String license) throws ProjectServletException {
logger.debug("createProject called in the servlet...");
try {
[1] projectService.createProject(path, name, summary, license);
} catch (Exception e) {
logger.error("unable to create project", e);
throw new ProjectServletException(e.getMessage());
}
}
(...)
[2] private static Project projectFromQualipsoProject(org.qualipso.factory.project.entity.Project qproject) {
Project project = new Project();
project.setPath(qproject.getResourcePath());
project.setName(qproject.getResourceName());
project.setLicence(qproject.getLicence());
(...)
return project;
}
}That Java file is contained in the "model/" folder where all the UI model objects are grouped. Here is an example of an object used in the Project module : a project has a name, a licence, etc. Only getters and setters are written here. The converter between that UI object and the server one has to be written in the ProjectServletImpl.java file.
@SuppressWarnings("serial")
public class Project implements Serializable {
private String path;
private String name;
private String licence;
public Project() {
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
(...)
}For example, CRUD operations (create, read, update, delete) for a Project resource implies 4 views (one for each action). Each of those views are declared using the @OPViewFactory annotation inside the Presenter.java and a couple of a "gwt-xml" UI file (ie. DisplayCreateEditView.ui.xml) and a java UI file (ie. DisplayCreateEditView.java) are necessary. These operations will allow to dynamically load standard view from anywhere in the UI.
That Java file is the entry point of the module. Its goal is to manage all the project views, to handle UI module events, to declare views to other UI modules and to receive views actions.
Those annotations have to be used :
| [1] @OPServiceName | Declare a class (an EntryPoint class) as the entry point of the module |
| [2] @OPViewFactory | Declare a view to be integrated inside the whole factory. Two parameters are mandatory : String resourceName: resource name String actionName: action name can be picked in the constant attributes of OPShell (ie. OPShel.ACTION_CREATE) |
| [3] @OPEventHandler | Declare an event handling method that can be called by any other module via the OPShell (OPShell.dispatchEventTo()). No specific events or an event or an array of events can be specified. |
To produce an event, you can use a specific class defined in the OpenParts framework: OPSHell, which is a kind of helper class including a lot of actions in the framework. Here are the three methods accessible :
| OPShell.displayView() | Call to OPShell for displaying a view of an external module for an embbed use (you can only display core views, otherwise you should be dependant of other unknown modules) |
| OPShell.dispatchEvent() | Call to OPShell for dispatch an event (declared in a nomenclature). Two parameters are mandatory : String event: event name (ie. "CHANGE_PATH", "UPDATE_PATH", "REFRESH") OPParams params: a couple of name and value |
| OPShell.dispatchEventTo() | Call to OPShell for dispatch an event to a specified module. Three parameters are mandatory : String service: external module service name ie. "project" String event: event name (ie. "CUSTOM_EVENT") OPParams params: a couple of name and value |
!!! ADD EXAMPLES INSIDE THAT PRESENTER OF ALL THE ANNOTATIONS AND OPSHELL CALLS !!!
[1]@OPServiceName("project")
public class ProjectPresenter implements EntryPoint {
public interface ProjectBinder extends OPBinder<ProjectPresenter> {}
public final static ProjectBinder binder = GWT.create(ProjectBinder.class);
private final ProjectServletAsync projectServlet = GWT.create(ProjectServlet.class);
private DisplayCreateEditView dceView = null;
[2] @OPViewFactory(resourceName="project", actionName=OPShell.ACTION_CREATE)
public Widget loadDisplayCreateEditView(OPParams params){
dceView = new DisplayCreateEditView(this);
return dceView;
}
@OPViewFactory(resourceName = "project", actionName = "menu")
public Widget loadProjectMenuActionItem(final OPParams params) {
return new ProjectMenuActionItem(this);
}
@Override
public void onModuleLoad() {
GWT.log("Loaded Project", null);
binder.bindPart(this);
binder.notifyLoadingCompleted();
}
[3] ???
}That "gwt-xml" file is the presentation part of a view. A view should be defined for each action and for each resource. Some QGWT components are used in that view. To be able to access to the QGWT framework, just declare the entry point of the QGWT project [1] and then use custom tags (cf. QGWT guidelines for more information).
If you are using standard GWT components, you must use only Layout components (cf. the official GWT documentation), otherwise it won't be displayed.
To be able to access to custom styles or physical resources, you have to declare the ProjectResources.java [2] which allows getting the specified resources using Java accessors.
!!! TO BE UPDATED DEPENDING ON INRIA : ONLY 1 VIEW FOR CRUD?? !!!
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui"
[1] xmlns:q="urn:import:org.qualipso.factory.ui.shared.QGWT.client.ui">
[2] <ui:with field='resources' type='org.qualipso.factory.ui.service.project.client.resources.ProjectResources' />
<q:QTabLayoutPanel ui:field="tabLayout">
<tab name="Generic">
<q:QFormPanel>
<elem name='Name' type='text' exemple='Qualipso'>Project Name</elem>
<elem name='Development Status' type='text' exemple='finished'>Development Status</elem>
<elem name='Description' type='area' exemple='Ultimate forge project'>Project Descrition</elem>
</q:QFormPanel>
</tab>
<tab name="Topics">
<q:QFormPanel>
<elem name='Topics' type='text' exemple='forge;java'>Topics</elem>
</q:QFormPanel>
</tab>
<tab name="Languages">
<q:QFormPanel>
<elem name='Programming Languages' type='text' exemple='c++;java'>Programming Languages</elem>
<elem name='Spoken Languages' type='text' exemple='english; french'>Spoken Languages</elem>
</q:QFormPanel>
</tab>
<tab name="Audiences">
<q:QFormPanel>
<elem name='Audiences' type='text' exemple='Loic Vourch'>Audiences</elem>
</q:QFormPanel>
</tab>
<tab name="Operating System">
<q:QFormPanel>
<elem name='OS' type='text' exemple='MacOS'>Operating system used</elem>
</q:QFormPanel>
</tab>
</q:QTabLayoutPanel>
</ui:UiBinder>That Java file is the behavior handling part of a view. It mainly does calls to the ProjectPresenter.java. It must extends Composite for allowing integration in the whole UI.
public class DisplayCreateEditView extends Composite{
interface DisplayCreateEditUiBinder extends UiBinder<Widget, DisplayCreateEditView>{}
private static DisplayCreateEditUiBinder uiBinder = GWT.create(DisplayCreateEditUiBinder.class);
@UiField
ProjectResources resources;
@UiField
QTabLayoutPanel tabLayout;
private ProjectPresenter presenter;
public DisplayCreateEditView(ProjectPresenter projectPresenter){
initWidget(uiBinder.createAndBindUi(this));
resources.style().ensureInjected();
presenter = projectPresenter;
for(Widget w : tabLayout.getPanels()){
((QFormPanel)w).setOkButtonHandler(new OkHandler());
}
}
private class CreateHandler implements ClickHandler{
@Override
public void onClick(ClickEvent arg0) {
for (Widget w : tabLayout.getPanels())
((QFormPanel)w).empty();
}
}
private class OkHandler implements ClickHandler{
@Override
public void onClick(ClickEvent arg0) {
for (Widget w : tabLayout.getPanels())
((QFormPanel)w).ok();
}
}
}Two ways are available for accessing physical resources.
The first one is the standard one, which is that you make an http request each time you want to get a resource. !! MORE EXPLANATIONS NEEDED!!
The second one is the specificity of GWT : resources are encapsuled inside the css or js files and so, are surely loaded inside the client browser. !! MORE EXPLANATIONS NEEDED!!
That Java file is an interface for accessing physical resources directly in the views files.
public interface ProjectResources extends ClientBundle {
@Source("org/qualipso/factory/ui/service/project/client/resources/css/style.css")
Style style();
@Source("org/qualipso/factory/ui/service/project/client/resources/img/loader.gif")
ImageResource iconLoader();
@Source("org/qualipso/factory/ui/service/project/client/resources/img/accept.png")
ImageResource iconGood();
(...)
}That Java file is an interface that allows accessing css style classes directly in the views files (ie. DisplayCreateEditView.ui.xml and DisplayCreateEditView.java). Those accessors must be the same as the css classes written in the style.css file.
public interface Style extends CssResource {
String mycustomstyle();
(...)
}That css file defines custom classes for display. You can also use specific GWT tags.
.mycustomstyle {
color: black;
border: 1px solid #ffffff;
background-color: white;
}To be done