miércoles, 22 de noviembre de 2017

Spring Data Rest: Many to Many relations with extra atrributes





In many articles can see that if we want work many to many relations with extra colmuns the post/put requests must is done from  Classroom or ClassType entities in several steps. Example:

curl -i -X POST -H "Content-Type:application/json"
  -d "{\"name\":\"Room 1\"}" http://localhost:8080/Classrooms

curl -i -X POST -H "Content-Type:application/json"
  -d "{\"name\":\"Aerobic\"}" http://localhost:8080/ClassTypes

curl -i -X PUT -H "Content-Type:text/uri-list"
  --data-binary @uris.txt http://localhost:8080/Classrooms/1/ClassTypes

The uris.txt file contains the URIs of the ClassTypes, each on a separate line:
http://localhost:8080/ClassTypes/1

The problem is when i need do this in one transaction. To do this I need change focus and use as repository the Classroom/ClassType entity. In this case i could do this curl request:

curl -i -X POST -H "Content-Type:application/json" -d '{"id":{"classroom":"1","classType":"1"}, "classMax": 80}' http://localhost:8762/classroomproxy/classroomClassTypes/1_1

The JPA entity definition:

Classroom/ClassType



The repository


As the relation Classroom/ClassType (N to M) doesn't allow null, both for Classroom and for ClassType must load the information before saving in the database. If that is not done hibernate will generate an error saying "null reference". With Spring Data Rest we can handle the  creation events. Before creating we can get the class information of entities relations and then set ClassroomClassType


Id Converter

To allow the customization of how entity ids are exposed in URIs generated.


Configuration






viernes, 4 de agosto de 2017

Overriding Spring Data REST Response Handlers to share news methods in all repositories

With my obsession for reusing all developments. I decided include all features from my module Spring Data Extension into Spring Data Rest Extension.  Hence, I can use all my extensions of spring data across rest in any proyect, without having to create mapping request in each one of the projects. In this line, I avoid repeat code and avoid have to do unit test by each repository that i make.

To explain how i have extended the spring data rest, i use like sample, the updating embedded documents that i made in spring data extension.

The more important is creates my own rest repository controller.

This class must have marked @RepositoryRestController annotation.



As we can see in the figure above, the mapping url is the union between BASE_MAPPING and "{id}/mergeembedded/{embeddedProperty}".


  • BASE_MAPPING: Contain the base path and repository name.
  • {id}: Identifier of the repository. In this point isn't used, but we'll see that the id is handled by PersistentEntityResourceHandlerMethodArgumentResolver.
  • {embeddedProperty}: Property name of the domain to get embedded information.


Another important thing in this class is get a invoker to execute, (in this case), the merge function. We get the invoker object using resourceInformation.getInvoker(). RootResourceInformation is the class that serves as bridge between repository rest controller and repository.

The next step is connect our repository invoker with RootResourceInformation. This connection is done by the class that resolves the arguments of the request.



The spring guys have provided us a method called postProcess where we can do this.

Spring Data Rest when processes the patch method does a merge between request content, (repository domain), and the persistent repository . This domain is sended to controller and is recoveried across getContext(). In my case, this operation don't want do it, because the merge embedded function already does this operation. I wan't get the original request domain in my custom controller. To change this behaviour, we have override PersistentEntityResourceHandlerMethodArgumentResolver.



Depending of a request header property and patch method is applied the update no merge feature or the default function.

The next step consists in says to spring data rest configuration that it uses the new funcionality. Hence, we override RepositoryRestMvcConfiguration. This class configure all necesary for spring data rest.



Finally, we going to create a configuration interface to do more easy the configuration to the developer.



How can we use this news features in our project?. We going to see a sample.

Steps:


  1. Define domains. Look at MongoDomain and MongoEmbeddedDomain.
  2. Define repository. Look at MongoRepository.
  3. Define @EnabledRepositoryExtensionRestMvc. Look at RestWebMvcConfiguration

Once defined all necesary in my project, we can use the news rest services methods across the curl utility or whatever. Look sample at integration test.


Full Sorce code.


martes, 28 de marzo de 2017

Custom adapters and runtime dynamic configuration with spring integration

Six month ago, We had develop a prototype to integrate differents ERP (enterprise resource planning) information across ftp system. One of the ERP used a rest endpoint to receive information. But the other ERP, could only use the ftp system to communicate information. it could not send information using rest endpoint.

Other issue of the project is that the client would want have the full control to configure ftp options. We had build a system where the client might configure the ftp options using a portal web. We had build a listener to detect the changes in the database configuration and create dynamicly the ftp adapters

We used spring integration to develop the system. The problem is that spring integration is not very prepared to create adapter in runtime and had not inbound jdbc adapters watcher.

How did we resolve that problems?. 

  • We build custom inbound jdbc adapters watcher. JdbcInMemoryChangeWatcherAdapter. The adapter  detects changes of a table and sending the change information to the next channel. The updating is detected using a stamp field (watchField). This adapter only accept primary key of string type. The adapter works comparing all physic table information with a memory table, therefore this adapter is only prepared for small tables (normally configuration tables). 
  • Once that the system receives the changes, we load inbound ftp configuration from xml file configuration. Look at DynamicFtpWatcher . The ftp dynamic options is loaded using environment properties. The only problem that we found is that the bean ids can't be populated using environment variables, and we needed identifier every ftp configuration with diferrents ids. To resolve this problem, we created  PreProcessorXmlApplicationContext. This class preprocess xml configuration, replacing bean ids, before of generating GenericXmlApplicationContext. We might have load the application context using diferrents context, In this way, we would have had conflict problems, but we wanted load the ftp context into the same parent to reuse channel, transforms, etc.

In the under figure, we can see the flow of the spring integration application



1) Configuration database watcher (IntegrationConfiguration)



2) Ftp dynamic configuration watcher (FtpDynamic.xml)



Is neccesary save configuration id into header enricher, so that after the system know how to route  the rest message, (shared by all configurations) to the correct channel.

3) Send ftp file to rest endpoint (IntegrationConfiguration)



4) Remove resources (FtpDynamic.xml)



Remove ftp file and sync folder file.

The channel is only executed if  the response of the rest service is "OK" and the id is equal to original configuration. Remind the rest channel is shared by all configurations

expression="headers.ftpId.equals('${id}') and headers.http_statusCode == T(org.springframework.http.HttpStatus).OK">

5) End (IntegrationConfiguration)


 
Source code prototype: https://github.com/davsuapas/SpringIntegrationDynamic