📜 ⬆️ ⬇️

Generating DTO and remote interfaces from Java to ActionScript

Given a web application in Java and Flex. Blaze DS or similar technology using AMF serialization is used for communication. On the server side and on the client side, DTO (data transfer objects) and remote services interfaces are explicitly or implicitly present. In such applications, there is the problem of synchronization of the DTO code between the client and the server. Of course, if the application is completely covered with tests, the out-of-sync between Java and ActionScript sources will be revealed during testing, but it is possible to get feedback even earlier - already at compile time.

To do this, you need to generate DTO and remote interfaces each time you build. If the interfaces of the DTO or remote interfaces on the server are changed, they will change accordingly on the client. This will let you know about the problems already at the compilation stage, without waiting for the tests, besides, it is impossible to exclude the possibility that the tests do not completely cover the source code.
To generate AS DTO, there are many libraries, for example, clear toolkit , pimento or Gas3 from Granite DS.

The Gas3 library is integrated into flexmojos , it allows you to edit templates for generating classes and looks more preferable. The following examples use this library.
')

DTO generation


For an example of generating a DTO, take the java class SampleDto .
 public class SampleDto { public String field1; public String field2; public String getField1() { return field1; } public void setField1(String field1) { this.field1 = field1; } public String getField2() { return field2; } public void setField2(String field2) { this.field2 = field2; } } 

git.example.com:jar:jar place this class in the maven module git.example.com:jar:jar .
We will generate ActionScript code in the git.example.com:flex:swf module. In order to generate AS code from SampleDto.java you need to add the following in the flexmojos setting:

 <plugin> <groupId>org.sonatype.flexmojos</groupId> <artifactId>flexmojos-maven-plugin</artifactId> <version>3.9</version> <extensions>true</extensions> <configuration> <includeJavaClasses> <includeJavaClass>com.example.*</includeJavaClass> </includeJavaClasses> </configuration> <executions> <execution> <id>generate</id> <goals> <goal>generate</goal> </goals> </execution> </executions> </plugin> 

All generated classes consist of two parts: the class itself and the ancestor (with the postfix Base). In our case, after generation, we get the classes SampleDto and SampleDtoBase . The main class is generated if a class with this name does not exist. Ancestor is always generated and overwrites the old file, if there is one. All additional functionality needs to be added only to the main class, if it exists, in this case its generator will not touch. If you add a new behavior to the Base class, then the generator will remove everything.

 [Bindable] [RemoteClass(alias="com.example.SampleDto")] public class SampleDto extends SampleDtoBase { } [Bindable] public class SampleDtoBase implements IExternalizable { public var _field1:String; public var _field2:String; public function set field1(value:String):void { _field1 = value; } public function get field1():String { return _field1; } public function set field2(value:String):void { _field2 = value; } public function get field2():String { return _field2; } public function readExternal(input:IDataInput):void { _field1 = input.readObject() as String; _field2 = input.readObject() as String; } public function writeExternal(output:IDataOutput):void { output.writeObject(_field1); output.writeObject(_field2); } } 

Remote interface generation


Now we will generate a remote interface. Create a module git.example.com:jar:jar java class SampleService and annotate it.

 @RemoteDestination(id = "sampleService", channel = "amf") public class SampleService { public final String sampleMethod1() { return "test output"; } public final String getSampleField1(@Param("sampleDto") final SampleDto sampleDto) { return sampleDto.getField1(); } public final String getSampleField2(@Param("sampleDto") final SampleDto sampleDto) { return sampleDto.getField2(); } @IgnoredMethod public final void ignoredMethod() { } } 

The @RemoteDestination describes the channel and the end point name for the service and hangs on the class. @Param sets the name of the parameter in the generated method, but it is optional. @IgnoredMethod tells the generator to ignore the marked method.
For generation we use the same settings as for DTO. At the output we get two classes:

 [RemoteClass(alias="com.example.SampleService")] public class SampleService extends SampleServiceBase { } public class SampleServiceBase extends RemoteObject { private var _initRemote:Boolean = false; private function initRemote():void { destination = "sampleService"; channelSet = new ChannelSet(); channelSet.addChannel(ServerConfig.getChannel("amf")); _initRemote = true; } public function sampleMethod1():void { if (!_initRemote) initRemote(); getOperation("sampleMethod1").send(); } public function getSampleField2(sampleDto:SampleDto):void { if (!_initRemote) initRemote(); getOperation("getSampleField2").send(sampleDto); } public function getSampleField1(sampleDto:SampleDto):void { if (!_initRemote) initRemote(); getOperation("getSampleField1").send(sampleDto); } public function addOperationListener(op:Function, type:String, handler:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void { if (op == this.sampleMethod1) this.getOperation("sampleMethod1").addEventListener(type, handler, useCapture, priority, useWeakReference); if (op == this.getSampleField2) this.getOperation("getSampleField2").addEventListener(type, handler, useCapture, priority, useWeakReference); if (op == this.getSampleField1) this.getOperation("getSampleField1").addEventListener(type, handler, useCapture, priority, useWeakReference); } public function removeOperationListener(op:Function, event:String, handler:Function):void { if (op == this.sampleMethod1) this.getOperation("sampleMethod1").removeEventListener(event, handler); if (op == this.getSampleField2) this.getOperation("getSampleField2").removeEventListener(event, handler); if (op == this.getSampleField1) this.getOperation("getSampleField1").removeEventListener(event, handler); } } 

Templates to generate


Gas3 allows you to modify templates for generation. Change the template for generating remote interfaces to be able to hang Responders for methods. Templates are written in groovy and quite voluminous, so they are not presented in the article; those who wish can see the templates in the repository for this example here . To modify templates, add the templates section to the flexmojos configuration:

 <templates> <base-remote-template> ${project.basedir}/src/main/generator-templates/remoteBase.gsp </base-remote-template> </templates> 

As a result, we get the following service code:

 public class SampleServiceBase extends RemoteObject { private static const logger:ILogger = Log.getLogger(getQualifiedClassName(SampleService).replace("::", ".")); private var _initRemote:Boolean = false; public function SampleServiceBase() { super(); } private function initRemote():void { destination = "sampleService"; channelSet = new ChannelSet(); channelSet.addChannel(ServerConfig.getChannel("amf")); _initRemote = true; } public function getSampleField1(sampleDto:SampleDto, responder:IResponder = null):AsyncToken { if (!_initRemote) { initRemote(); } var asyncToken:AsyncToken = getOperation("getSampleField1").send(sampleDto); if (responder) { asyncToken.addResponder(responder); } if (Log.isDebug()) { logger.debug("Method <getSampleField1> invoked with parameters <{0}>", sampleDto); } return asyncToken; } public function sampleMethod1(responder:IResponder = null):AsyncToken { if (!_initRemote) { initRemote(); } var asyncToken:AsyncToken = getOperation("sampleMethod1").send(); if (responder) { asyncToken.addResponder(responder); } if (Log.isDebug()) { logger.debug("Method <sampleMethod1> invoked with parameters "); } return asyncToken; } public function getSampleField2(sampleDto:SampleDto, responder:IResponder = null):AsyncToken { if (!_initRemote) { initRemote(); } var asyncToken:AsyncToken = getOperation("getSampleField2").send(sampleDto); if (responder) { asyncToken.addResponder(responder); } if (Log.isDebug()) { logger.debug("Method <getSampleField2> invoked with parameters <{0}>", sampleDto); } return asyncToken; } public function addOperationListener(op:Function, type:String, handler:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void { if (op == this.getSampleField1) { this.getOperation("getSampleField1").addEventListener(type, handler, useCapture, priority, useWeakReference); } if (op == this.sampleMethod1) { this.getOperation("sampleMethod1").addEventListener(type, handler, useCapture, priority, useWeakReference); } if (op == this.getSampleField2) { this.getOperation("getSampleField2").addEventListener(type, handler, useCapture, priority, useWeakReference); } } public function removeOperationListener(op:Function, event:String, handler:Function):void { if (op == this.getSampleField1) { this.getOperation("getSampleField1").removeEventListener(event, handler); } if (op == this.sampleMethod1) { this.getOperation("sampleMethod1").removeEventListener(event, handler); } if (op == this.getSampleField2) { this.getOperation("getSampleField2").removeEventListener(event, handler); } } } 

In addition to remote interfaces, Gas3 makes it possible to change templates for java interfaces, for regular beans and for JPA entity beans.

Total


As a result:
  1. We got the opportunity to learn about the inconsistency of java and actionscript code. If java code is changed, actionscript will change automatically, and all parts of the code that work with the old version of classes will not be able to compile.
  2. We have eliminated the need to manually generate AS DTO and remote interfaces.

For convenience, I created a web application that works with these entities. Source code can be viewed here .

Materials




ps I am aware of bad trends in flash / flex, but my experience may be useful to someone.

Source: https://habr.com/ru/post/144872/


All Articles