Monday, January 26, 2015

How to use DTO Factory in Eclipse Che

What is a DTO?

Data transfer objects are used in Che to do the communication between client and server. In a code level, this is just an interface annotated with @DTO com.codenvy.dto.shared.DTO. This interface should contain getters and setters (with bean naming conventions) for each and every fields that we need in this object.

 For example, following is a DTO with a single String field.

@DTO
public interface HelloUser {
String getHelloMessage();
void setHelloMessage(String message);
}

By convention, we need to put these DTOs to shared package as it will be used by both client and server side. 

DTO Factory 

DTO Factory is a factory available for both client and server sides, which can be used to serialize/deserialize DTOs. DTO factory internally uses generated DTO implementations (described in next section) to get this job done. Yet, it has a properly encapsulated API and developers can simply use DTOFactoy instance directly. 

For client side   : com.codenvy.ide.dto.DtoFactory
For server side  : com.codenvy.dto.server.DtoFactory

HelloUser helloUser = DtoFactory.getInstance().createDto(HelloUser.class);

Above code snippet shows how to initialize a DTO using DTOFactory. As mentioned above, proper DtoFactory classes should be used by client or server sides. 

Deserializing in client side

//important imports
import com.codenvy.ide.rest.AsyncRequestCallback;
import com.codenvy.ide.rest.DtoUnmarshallerFactory;
import com.codenvy.ide.rest.Unmarshallable;

//invoke helloService
Unmarshallable<HelloUser> unmarshaller = unmarshallerFactory.newUnmarshaller(HelloUser.class);

helloService.sayHello(sayHello, new AsyncRequestCallback<HelloUser>(unmarshaller) {
@Override
protected void onSuccess(HelloUser result) {
}
@Override
protected void onFailure(Throwable exception) {
}
});

When invoking a service that returns a DTO, client side should register a callback created using relevant unmarshaller factory. Then, the on success method will be called with a deserialized DTO. 

De-serializing in server side

@Path("world")
@Consumes(MediaType.APPLICATION_JSON)
@Produces("application/json")
@POST
public ... sayHello(SayHello sayHello){
...
... sayHello.getHelloMessage() ...
...
}

Everest (JAX-RS implementation of Che) implementation automatically deserialize DTOs when they are used as parameters in rest services. It will identify serialized DTO with marked type -  @Consumes(MediaType.APPLICATION_JSON)  - and use generated DTO implementations to deserialize DTO. 

DTO maven plugin

As mentioned earlier, for DtoFactoy to function properly, it needs some generated code that will contain concrete logic to serialize/deserialize DTOs. GWT compiler should be able to access generated code for client side and generated code for server side should go in jar file.

Che uses a special maven plugin called “codenvy-dto-maven-plugin” to generate these codes. Following figure illustrates a sample configuration of this plugin. It contains separate executions for client and server sides. 

We have to input correct package structures accordingly and file paths to which these generated files should be copied. 

<groupId>com.codenvy.platform-api</groupId>
<artifactId>codenvy-dto-maven-plugin</artifactId>
<version>${codenvy.platform-api.version}</version>
<executions>
<execution>
<id>generate-client</id>
<phase>process-sources</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<dtoPackages>
<package>org.wso2.dss.shared.dto</package>
</dtoPackages>
<outputDirectory>${dto-generator-out-directory}</outputDirectory>
<genClassName>org.wso2.dss.client.DtoClientImpls</genClassName>
<impl>client</impl>
</configuration>
</execution>
<execution>
<id>generate-server</id>
<phase>process-sources</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<dtoPackages>
<package>org.wso2.dss.shared.dto</package>
</dtoPackages>
<outputDirectory>${dto-generator-out-directory}</outputDirectory>
<genClassName>org.wso2.dss.server.DtoServerImpls</genClassName>
<impl>server</impl>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
</dependency>
<!--
 Other dependencies if DTOs from current project need them.
 -->
</dependencies>

package - package, in which, DTO interfaces resides
outputDirectory -  directory, to which, generated files should be copied
genClassName - class name for the generated class

You should also configure your maven build to use these generated classes as a resource when compiling and packaging. Just add following line in resources in build section.

<resource>
<directory>${dto-generator-out-directory}</directory>
</resource>

GWT MVP Implementation in Eclipse Che

MVP Pattern

Model View Presenter (aka MVP) is a design pattern that attempts to decouple the logic of a component from its presentation. This is similar to the popular MVC (model view controller) design pattern, but has some fundamentally different goals. The benefits of MVP include more testable code, more reusable code, and a decoupled development environment.

MVP Implementation in Che

Note : Code example used in this document are from a sample project wizard page for WSO2 DSS .

There are four main java components used to implement a Che component that follows MVP.

  1. Interface for View functionality
  2. Interface for Event delegation
  3. Implementation of View
  4. Presenter

Interfaces

To reduce the number of files created for each MVP component, No. 1 and No. 2 are created within a single java file. To be more precise, event delegation interface is defined as a sub interface within view interface.

View interface should define methods that will be used by presenter to communicate with view implementation. Event delegation interface should define methods that will be implemented by presenter so that view can delegate events to presenter using these methods.

Following code snippet demonstrates these two interfaces that we created for DSS project wizard page.

@ImplementedBy(DSSConfigurationViewImpl.class)
public interface DSSConfigurationView extends View<DSSConfigurationView.ActionDelegate> {
    String getGroupId();
    void setGroupId(String groupId);
    String getArtifactId();
    void setArtifactId(String artifactId);
    String getVersion();
    void setVersion(String version);

    interface ActionDelegate {
           void onGroupIdChanged();
           void onArtifactIdChanged();
           void onVersionChanged();
    }
}
VIew and Event Handler interfaces

Interface for view should extend from com.codenvy.ide.api.mvp.View interface. This com.codenvy.ide.api.mvp.View interface only defines a single method - void setDelegate(T var1).

…interface DSSConfigurationView extends View<DSSConfigurationView.ActionDelegate>..

Using generics, we need to inform this super interface about our event handling delegation interface.

View Implementation

View implementation often can extend from any abstract widget such as Composite. It may also use UIBinder  to implement the UI if necessary. It is possible to implement view by following any approach and using any GWT widget. The only must is that it should implement view interface (created in previous step) and IsWidget interface (Or extend any subclass of IsWidget).

public class DSSConfigurationViewImpl extends ... implements DSSConfigurationView {
      ...

      // Maintain a reference to presenter
    private ActionDelegate delegate;
   
      // provide a setter for presenter
    @Override
    public void setDelegate(ActionDelegate delegate) {
        this.delegate = delegate;
    }
      …
      // Implement methods defined in view interface
    @Override
    public String getGroupId() {
        return groupId.getText();
    }

    @Override
    public void setGroupId(String groupId) {
        this.groupId.setText(groupId);
    }
      …
      …
      // Notify presenter on UI events using delegation methods
    @UiHandler("groupId")
    public void onGroupIdChanged(KeyUpEvent event) {
        delegate.onGroupIdChanged();
    }

    @UiHandler("artifactId")
    public void onArtifactIdChanged(KeyUpEvent event) {
        delegate.onArtifactIdChanged();
    }

    ...
}
View implementation


As shown in above code snippet (see full code), main things to do in view implementation can be summarised as below.
  1. Extend any widget from GWT and implement user interface by following any approach
  2. Implement view interface (created in previous step)
  3. Manage a reference to action delegate (presenter - see next section for more info)
  4. Upon any UI events inform presenter using the delegation methods so that presenter can execute business logic accordingly   

Presenter

Presenter can extend from many available abstract presenters such as AbstractWizardPage, AbstractEditorPresenter and BasePresenter, anything that implements com.codenvy.ide.api.mvp.Presenter. It also should implement Action Delegation interface so that upon any UI events, those delegation methods will be called.


public class DSSConfigurationPresenter extends ... implements DSSConfigurationView.ActionDelegate {

      // Maintain a reference to view
    private final DSSConfigurationView view;
    ...

    @Inject
    public DSSConfigurationPresenter(DSSConfigurationView view, ...) {
         ...
         this.view = view;
         // Set this as action delegate for view
         view.setDelegate(this);
         ...
    }
      …

     // Init view and set view in container
    @Override
    public void go(AcceptsOneWidget container) {   
        view.setArtifactID("");
        view.setGroupID("");
        view.setVersion("");
        container.setWidget(view);
    }
      …

     // Execute necessary logic upon ui events
    @Override
    public void onGroupIdChanged() {
          delegate.updateControls();
    }

      // Execute necessary logic upon ui events
    @Override
    public void onArtifactIdChanged() {
         delegate.updateControls();
    }
      …
}
            Presenter

Depending on the extending presenter, there may be various abstract method that needs to be implemented by presenter. For example, if you extend AbstractEditorPresenter, you need to implement initializeEditor(), isDirty() and doSave(), etc. methods. If it is AbstractWizardPage, you need to implement isCompleted(), storeOptions(), removeOptions(), etc methods.

Yet, as shown in above code snippet (seefullcode), following are the main things that you need to do in presenter.
  1. Extend any abstract presenter as needed and implement abstract methods/or override behaviour as needed
  2. Implement Action delegation interface
  3. maintain a reference to view
  4. set this as the action delegate of view using set delegate method
  5. init view and set view in the parent container (go method)
  6. use methods defined in view interface to communicate with view

The go method is the one that will be called by Che UI framework, when this particular component is need to be shown in IDE. This method will be called with a reference to parent container.