I am creating an application that does not have a database inside (the database is located on the server that I will access). In this regard, I need to create an elastic search query based on the POJO object I have. For example, I have a class:
#AllArgsConstructor
class PersonalData{
private String lastName;
private String name;
}
and
#AllArgsConstructor
class Person{
private long id;
private PersonalData personalData;
}
Let's say there are objects of these classes:
Person person = new Person(1, new PersonalData("lastName", "name"));
From these classes, I would like to create an elastic query, for example:
"query": {
"bool": {
"filter": [
{
"term": {
"person.id.value": {
"value": 0,
"boost": 1.0
}
}
},
{
"wildcard": {
"person.personalData.lastName.value": {
"value": "lastName",
"boost": 1.0,
}
}
},
{
"wildcard": {
"person.personalData.name.value": {
"value": "name",
"boost": 1.0
}
}
}
]
}
}
I can create a request in this format like this:
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
.filter(
QueryBuilders.termQuery("person.id.value", person.getId()))
.filter(QueryBuilders.wildcardQuery("person.personalData.lastName.value", person.getPersonalData().getLastName()))
.filter(QueryBuilders.wildcardQuery("person.personalData.lastName.value", person.getPersonalData().getName())));
SearchSourceBuilder result = new SearchSourceBuilder();
result.query(queryBuilder);
However, this method does not suit me, I need to create a request based on a java object.
Further, I will need to transfer the resulting request in SUCH FORM via API to another back. Please help me, how can I get this request?
Related
I have this object class that has a list of customers as an attribute:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class PeopleDTO {
private String processType;
private String operation;
private String entity;
private String entityType;
private Long id;
private Document document;
#Getter
#Setter
class Customer {
private String systemId;
private String customerId;
}
private List<Customer> customers;
}
This list is retrieved calling another microservice using webclient as follows:
public Mono<CuCoPerson> getCuCoPerson(Integer cucoId, String GS_AUTH_TOKEN) {
WebClient webClient = WebClient.create();
return webClient.get()
.uri(GET_RELATION_BY_ID + cucoId)
.header("Accept", "application/json")
.header("Authorization", GS_AUTH_TOKEN)
.retrieve()
.bodyToMono(CuCoPerson.class)
.map(cuCoPerson -> {
List<CustomerRelation> matches = cuCoPerson.getRelatedCustomers()
.stream()
.filter(relation -> relation.getSystemId().equals(400) || relation.getSystemId().equals(300) || relation.getSystemId().equals(410))
.filter(relation -> relation.getCustomerId().contains("F"))
.collect(Collectors.toList());
cuCoPerson.setRelatedCustomers(matches);
return cuCoPerson;
});
}
This method return a cucoPerson as follows:
{
"id": 1,
"relatedCustomers": [
{
"customerId": "xxx",
"systemId": 999
}
]
}
So now I want to add this object to my PeopleDTO class, but I don't know how. This is what I've done son far (hardcoded):
public PeopleDTO createPeople(Long id) {
PeopleDTO people = new PeopleDTO();
people.setProcessType("ONLINE");
people.setOperation("UPDATE");
people.setEntity("DOCUMENT");
people.setEntityType("DOCUMENT");
people.setIdCuco(id);
people.setDocument(new Document());
people.setCustomers(......);
}
So as you can see I don't know how to add a Mono in the last line.
The expected result should be like this:
{
"type": "ONLINE",
"operation": "UPDATE",
"id": 1,
"entity": "DOCUMENT",
"entityType": "NIE",
"documents": {
"id": 1,
"additionals": {
"issuing_authority": "Spain",
"country_doc": "ES",
"place_of_birth": "",
"valid_from": "1995-08-09",
"valid_to": "0001-01-01"
},
"code": "X12345",
"typeDocument": "NIE"
},
"id": 1,
"relatedCustomers": [
{
"customerId": "xxx",
"systemId": 999
}
]
}
first, create a list of customers like:
List<Customer> customers=new ArrayList<>;
Then add all the Customers to it one by one using a loop,
then you can directly add that to your object like
people.setCustomers(customers);
your object assignment should look something like:
public PeopleDTO createPeople(Long id) {
PeopleDTO people = new PeopleDTO();
people.setProcessType("ONLINE");
people.setOperation("UPDATE");
people.setEntity("DOCUMENT");
people.setEntityType("DOCUMENT");
people.setIdCuco(id);
people.setDocument(new Document());
List<Customer> customers=new ArrayList<>;
//add data to customer
people.setCustomers(customers);
}
How can I use MapStruct to create a mapper that maps from Model entity that includes one list of objects and one another object to Domain entity, consists of only list of nested objects.
My Model entity list object = SourceObject-A;
My Model entity second object = SourceObject-B;
My Doamin entity list object = TargetObject-AB;
My source classes looks like this:
SourceObject-A:
#NoArgsConstructor
#AllArgsConstructor
#Data
public class User {
private int id;
private String name;
}
SourceObject-B:
#NoArgsConstructor
#AllArgsConstructor
#Data
public class CountryDetails {
private String country;
private String countryCode;
}
So I need to tranform it to this(TargetObject-AB):
#NoArgsConstructor
#AllArgsConstructor
#Data
public class DomainUser {
private int id;
private String name;
private String country;
private String countryCode;
}
UserController:
GetMapping("/users")
public List<DomainUser> getDUsers(List<User> usersList, CountryDetails countryDetails){
List<DomainUser> domainUsersList=ModelToDomainMapper.INSTANCE.UserToDUser(usersList,
countryDetails);
return domainUsersList;
}
Mapper Interface:
#Mapper
public interface ModelToDomainMapper {
ModelToDomainMapper INSTANCE = Mappers.getMapper(ModelToDomainMapper.class)
List<DomainUser> UserToDUser(List<User> users, CountryDetails countryDetails);
}
Expected sample json:
Source(Input):
[
"countryDetails":{
"country" : "India",
"countryCode" : "+91"
},
"userslist" :[
{
"id" : 1,
"name" : "XXXXXXX"
},
{
"id" : 2,
"name" : "XXXXXXX"
}
]
]
Target(Expected Output):
[
{
"id": 1,
"name": "xxxxxx",
"country": "India",
"countryCode": "+91"
},
{
"id": 2,
"name": "xxxxxx",
"country": "India",
"countryCode": "+91"
}
]
Is there any way to get this above output please help me.
You could do something like this in your mapper:
DomainUser UserToDUser(User user, CountryDetails countryDetails);
default List<DomainUser> UsersToDomainUsers(List<User> users, CountryDetails countryDetails) {
return users.stream()
.map((user -> UserToDUser(user, countryDetails)))
.collect(Collectors.toList());
}
This would use the MapStruct generated mapper to merge every single User with the same CountryDetails to create a DomainUser and collect them all into a List using streams.
I have an issue problem with the inclusion of nested class in the aggregation.
This is a preview of the json document in my collection :
{
"id": "1234",
"typeApp": "API",
"name": "name",
"versionNum": "1",
"release": {
"author": "name",
//some other data
}
}
The document java class :
#Document(collection = "myClassExamples")
public class MyClassExampleDocument {
#Id
private String id;
private String typeApp;
private String name;
private String versionNum;
private Release release;
public static class Release {
private String author;
//Other fields...
}
}
I am trying to build a query, to find the last documents group by a given typeApp in parameter, and sort by versionNum DESC to get the new one by typeApp.
I started with an easier query, a simple group by typeApp :
Aggregation aggregation = newAggregation(
Aggregation.sort(Sort.Direction.DESC, "versionNum"),
Aggregation.group("typeApp"),
project(MyClassExampleDocument.class)
)
The query returns a list of MyClassExampleDocument, with all fields with null values except for the id which is populated with the typeApp.
Do you know how to build the aggregation in order to get the entire object, as stored in my collection ?
Thanks for the help !
You can use like following
public List<MyClassExampleDocument> test() {
Aggregation aggregation = Aggregation.newAggregation(
sort(Sort.Direction.DESC, "versionNum"),
group("typeApp").first("$$ROOT").as("data")
replaceRoot("data")
).withOptions(AggregationOptions.builder().allowDiskUse(Boolean.TRUE).build());
return mongoTemplate.aggregate(aggregation, mongoTemplate.getCollectionName(MyClassExampleDocument.class), MyClassExampleDocument.class).getMappedResults();
}
here is the aggregation
db.collection.aggregate([
{ "$sort": { versionNum: -1 } },
{
"$group": {
"_id": "$typeApp",
"data": { "$first": "$$ROOT" }
}
},
{ "$replaceRoot": { "newRoot": "$data" } }
])
Working Mongo playground
Note : The java code was not tested, it was implemented based on working mongo script
I'm setting up a P.O.C. using Neo4j, and technically have everything I need working but would like it set up properly.
As a quick overview - I can create nodes and relationships, and traverse the graph (i.e. return all features available in a specific market) so I know these nodes/relationships have been created.
However, when I query to simply return a Node based on ID, it returns ONLY the data for that node - and not any relationships or connected nodes, for example, the markets its available in.
I've looked various places online that have not only a Node returned but also the subsequent nodes - though I follow what they're doing I cant seem to get it to work with mine.
Feature Repository:
#Repository
public interface FeatureRepository<T extends Feature> extends Neo4jRepository<T, Long> {
...
}
Colour Repository:
#Repository
public interface ColourRepository extends FeatureRepository<Colour>{
#Query("CREATE(feat:Colour:Feature {marketingDesc:{marketing}, engineeringDesc:{engineering}, code:{code}})")
Colour createColour(#Param("marketing") String marketingDesc, #Param("engineering") String engineeringDesc, #Param("code") String code);
#Query("MATCH (c:Colour {code:{colourCode}}) MATCH (c)-[:AVAILABLE_IN]->(market) RETURN c AS colour, COLLECT(market) AS markets")
Colour getColourByCode(#Param("colourCode") String colourCode);
Colour findByCode(#Param("code") String code);
}
Feature Entity:
#NodeEntity(label = "Feature")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Feature {
#Id
#GeneratedValue
private Long id;
private String marketingDesc;
private String engineeringDesc;
#Index(unique = true)
private String code;
#Relationship(type = "HAS_OPTION", direction = Relationship.INCOMING)
private List<Option> options = new ArrayList<>();
#Relationship(type = "AVAILABLE_IN")
private List<Market> markets = new ArrayList<>();
#Relationship(type = "HAS_PREREQUISITE", direction = Relationship.UNDIRECTED)
private List<Prerequisite> prerequisites = new ArrayList<>();
}
Colour Entity:
#AllArgsConstructor
#NodeEntity(label = "Colour")
public class Colour extends Feature {
}
Market Entity:
#NodeEntity(label = "Market")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Market {
#Id
#GeneratedValue
private Long id;
#Index(unique = true)
private String code;
private String market;
#Relationship(type = "AVAILABLE_IN", direction = Relationship.INCOMING)
private List<Option> features = new ArrayList<>();
}
Relationship Entity (for features to be connected to markets they can be bought in):
#RelationshipEntity(type = "AVAILABLE_IN")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Available {
#Id
#GeneratedValue
private Long Id;
private List<String> availableIn = new ArrayList<>();
#StartNode
private Feature feature;
#EndNode
private Market market;
}
Controller:
#RestController
public class ConfigController {
private final Handler configHandler;
public ConfigController(Handler configHandler) {
this.configHandler = configHandler;
}
#PostMapping(path = "/create/colour", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public SimpleResponse createColour(#RequestBody Colour request) {
ColourService service = new ColourService(configHandler);
Colour created = service.createColour(request);
return SimpleResponse.builder().result("Created:", created).build();
}
#PostMapping(path = "/create/market", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public SimpleResponse createMarket(#RequestBody Market request) {
MarketService service = new MarketService(configHandler);
Market created = service.createMarket(request);
return SimpleResponse.builder().result("Created", created).build();
}
#PostMapping(path = "/create/relationship/availableIn", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public SimpleResponse createAvailableInRelationship(#RequestBody OptionAvailInRequest request){
RelationshipService service = new RelationshipService(configHandler);
Object result = service.createAvailableInRelationship(request);
return SimpleResponse.builder().result("Result:", result).build();
}
#GetMapping(path = "/colour/{code}")
public SimpleResponse getColourByCode(#PathVariable(value = "code") String code) {
ColourService service = new ColourService(configHandler);
Colour colour = service.getColourByCode(code);
return SimpleResponse.builder().result("Colour:", colour).build();
}
#GetMapping(path = "/features/available/{mrktCode}")
public SimpleResponse getFeaturesInMarket(#PathVariable(value = "mrktCode") String mrktCode){
RelationshipService service = new RelationshipService(configHandler);
Collection<Feature> features = service.getFeaturesInMarket(mrktCode);
return SimpleResponse.builder().result("Features:", features).build();
}
}
Neo4jConfig file:
#Configuration
#EnableNeo4jRepositories(basePackages = "package.location")
#EnableTransactionManagement
public class Neo4jConfig {
#Bean
public org.neo4j.ogm.config.Configuration configuration() {
org.neo4j.ogm.config.Configuration configuration =
new org.neo4j.ogm.config.Configuration.Builder().build();
return configuration;
}
#Bean
public SessionFactory sessionFactory(org.neo4j.ogm.config.Configuration configuration) {
return new SessionFactory(configuration,"package.location");
}
#Bean
public Neo4jTransactionManager transactionManager(SessionFactory sessionFactory) {
return new Neo4jTransactionManager(sessionFactory);
}
}
So, for example, here I can create a Colour Node:
Example value:
{
"code": "string",
"engineeringDesc": "string",
"id": 0,
"marketingDesc": "string",
"markets": [
{
"code": "string",
"features": [
{}
],
"id": 0,
"market": "string"
}
],
"options": [
{}
],
"prerequisites": [
{}
]
}
What I send:
{
"code": "BLU",
"engineeringDesc": "Blue engineering",
"marketingDesc": "Blue marketing"
}
And this creates a Colour Node successfully:
{
"result": {
"Created:": {
"id": 0,
"marketingDesc": "Blue marketing",
"engineeringDesc": "Blue engineering",
"code": "BLU",
"options": [],
"markets": [],
"prerequisites": []
}
},
"error": null
}
I can create a Market Node:
Example Value:
{
"code": "string",
"features": [
{}
],
"id": 0,
"market": "string"
}
What I send:
{
"code": "UB",
"market": "England"
}
Which creates a Market Node successfully:
{
"result": {
"Created": {
"id": 1,
"code": "UB",
"market": "England",
"features": []
}
},
"error": null
}
I can then create a relationship between the two, to say that colour is available in that market:
{
"featureCode": "BLU",
"marketCode": "UB"
}
Which I can verify has been created by hitting:
localhost:8080/features/available/UB
{
"result": {
"Features:": [
{
"id": 0,
"marketingDesc": "Blue marketing",
"engineeringDesc": "Blue engineering",
"code": "BLU",
"options": [],
"markets": [],
"prerequisites": []
}
]
},
"error": null
}
However when I then go to return the Colour Node itself:
localhost:8080/colour/BLU
{
"result": {
"Colour:": {
"id": 0,
"marketingDesc": "Blue marketing",
"engineeringDesc": "Blue engineering",
"code": "BLU",
"options": [],
"markets": [],
"prerequisites": []
}
},
"error": null
}
The 'markets' option is always null. I have tried custom queries and building queries using the neo4j helper (e.g. findByCode etc.), and every example I can find will sucessfully return the related nodes, but I cant seem to get mine to.
Can anyone help?
P.S. Please let me know if there is anything else that would be helpful for you to see. Been trying to get this sorted for days....
Got the answer to this question...
Feature Entity should have been:
#Relationship(type = "AVAILABLE_IN")
#ApiModelProperty(hidden = true)
private Set<Available> markets = new HashSet<>();
Market Entity should have been:
#Relationship(type = "AVAILABLE_IN", direction = Relationship.INCOMING)
#ApiModelProperty(hidden = true)
private Set<Available> features = new HashSet<>();
Which gets the markets section of the feature JSON no longer null...
Now I have the problem that there's an infinite recursion loop between the two classes, with a feature displaying the markets and the markets displaying the features
EDIT:
For anyone else with this/similar issues, I've found a really good github resource.
GitHub neo4j ogm walkthrough
Helped a lot.
I am in the process of evaluating Spring Data REST as a backend for an AngularJS based application. I quickly modeled our domain as a set of aggregate roots and hit the following design roadblock:
Model Resource has
multiple Task Entities
referencing multiple Attribute Resources
I expected the HAL _links for the attributes to be placed inside each of the task JSON object, but sadly the attributes are only visible as a link at the root of the JSON construct.
E.g. I get this:
{
"version": 0,
"name": "myModel",
"tasks": [
{
"name": "task1"
},
{
"name": "task2"
}
],
"_links": {
"self": {
"href": "http://localhost:8080/models/1"
},
"attributes": {
"href": "http://localhost:8080/models/1/attributes"
}
}
}
Instead of something I would image could be like:
{
"version": 0,
"name": "myModel",
"tasks": [
{
"name": "task1",
"_links": {
"attributes": {
"href": "http://localhost:8080/models/1/tasks/1/attributes"
}
}
},
{
"name": "task2",
"_links": {
"attributes": {
"href": "http://localhost:8080/models/1/tasks/2/attributes"
}
}
],
"_links": {
"self": {
"href": "http://localhost:8080/models/1"
},
"attributes": {
"href": "http://localhost:8080/models/1/attributes"
}
}
}
Incidentally, in the first example, the attributes link ends in a 404.
I haven't seen anything in the HAL spec to handle this kind of cases, nor in the Spring Data REST documentation. Obviously, I could define the task as a resource to workaround the problem, however my model does not require this. I feel like this is a legitimate use case.
I created a simple Spring Boot application that reproduces this issue. The models:
#Entity
public class Model {
#Id #GeneratedValue public Long id;
#Version public Long version;
public String name;
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
public List<Task> tasks;
}
#Entity
public class Task {
#Id #GeneratedValue public Long id;
public String name;
#ManyToMany
public Set<Attribute> attributes;
}
#Entity
public class Attribute {
#Id #GeneratedValue public Long id;
#Version public Long version;
public String name;
}
And repositories:
#RepositoryRestResource
public interface ModelRepository extends PagingAndSortingRepository<Model, Long> {
}
#RepositoryRestResource
public interface AttributeRepository extends PagingAndSortingRepository<Attribute,Long> {
}
There, I may have missed something as this seems like quite a simple use case but could not find anyone with a similar problem on SO. Also, maybe this is a fundamental flaw in my model, and if so I'm ready to hear your arguments :-)
Because Spring Data REST does not handle natively the use case described in the question, the first step is to deactivate the management of the Task's Attributes, and ensure they are not serialized by default. Here the #RestResource(exported=false) ensures that a (non working) link will not get automatically generated for an "attributes" rel, and the #JsonIgnore ensures that attributes will not be rendered by default.
#Entity
public class Task {
#Id
#GeneratedValue
public Long id;
public String name;
#ManyToMany
#RestResource(exported = false)
#JsonIgnore
public List<Attribute> attributes;
}
Next, the _links attribute is only available at the root of our resource, so I chose to implement a new rel named "taskAttributes", that will have multiple values, one for each of the task. To add those links to the resource, I built a custom ResourceProcessor, and to implement the actual endpoints, a custom ModelController:
#Component
public class ModelResourceProcessor implements ResourceProcessor<Resource<Model>> {
#Override
public Resource<Model> process(Resource<Model> modelResource) {
Model model = modelResource.getContent();
for (int i = 0; i < model.tasks.size(); i++) {
modelResource.add(linkTo(ModelController.class, model.id)
.slash("task")
.slash(i)
.slash("attributes")
.withRel("taskAttributes"));
}
return modelResource;
}
}
#RepositoryRestController
#RequestMapping("/models/{id}")
public class ModelController {
#RequestMapping(value = "/task/{index}/attributes", method = RequestMethod.GET)
public ResponseEntity<Resources<PersistentEntityResource>> taskAttributes(
#PathVariable("id") Model model,
#PathVariable("index") int taskIndex,
PersistentEntityResourceAssembler assembler) {
if (model == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
if (taskIndex < 0 || taskIndex >= model.tasks.size()) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
List<Attribute> attributes = model.tasks.get(taskIndex).attributes;
List<PersistentEntityResource> resources = attributes.stream()
.map(t -> assembler.toResource(t))
.collect(toList());
return ResponseEntity.ok(new Resources(resources));
}
}
This makes a call to http://localhost:8080/api/models/1 return something like this:
{
"name": "myModel",
"tasks": [
{
"name": "task1"
},
{
"name": "task2"
}
],
"_links": {
"self": {
"href": "http://localhost:8080/models/1"
},
"model": {
"href": "http://localhost:8080/models/1{?projection}",
"templated": true
},
"taskAttributes": [
{
"href": "http://localhost:8080/models/1/task/0/attributes"
},
{
"href": "http://localhost:8080/models/1/task/1/attributes"
}
]
}
}
Finally, to make all this more usable from the UI, I added a Projection on the Model resource:
#Projection(name = "ui", types = {Model.class, Attribute.class})
public interface ModelUiProjection {
String getName();
List<TaskProjection> getTasks();
public interface TaskProjection {
String getName();
List<AttributeUiProjection> getAttributes();
}
public interface AttributeUiProjection {
String getName();
}
}
Which lets one get a subset of the Attribute's properties without the need to fetch them from the "taskAttributes" rel:
http://localhost:8080/api/models/1?projection=ui returns something like this:
{
"name": "myModel",
"tasks": [
{
"name": "task1",
"attributes": [
{
"name": "attrForTask1",
"_links": {
"self": {
"href": "http://localhost:8080/attributes/1{?projection}",
"templated": true
}
}
}
]
},
{
"name": "task2",
"attributes": [
{
"name": "attrForTask2",
"_links": {
"self": {
"href": "http://localhost:8080/attributes/2{?projection}",
"templated": true
}
}
},
{
"name": "anotherAttrForTask2",
"_links": {
"self": {
"href": "http://localhost:8080/attributes/3{?projection}",
"templated": true
}
}
},
...
]
}
],
"_links": {
"self": {
"href": "http://localhost:8080/models/1"
},
"model": {
"href": "http://localhost:8080/models/1{?projection}",
"templated": true
}
}
}
You do not have a repository for Tasks - in spring data rest you do not have a controller if you do not have a repository. I think you would get a link if a task would just hold one attribute - but you have a Set - so the access to the attributes would be a sub-resource of the task resource.
So your scenario just does not work. I would try to have a TaskRepository that you export and remove the attribute repository.
Then your model resource would contain the link to its task and the task resource would embed the attributes.
You could work with projections if you still wanted to have the tasks inlined into your model resource.