mapping list source to target - spring-boot

I have an object organization that contains a contact objects list.
The conacts list exists already in the database.
To test with postman, when i need to add an organization i have to add the list of contacts id
{
"name": "ce",
"contactsId": [1, 3]
}
In the contact class i have
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "contact_organization", joinColumns = #JoinColumn(name = "contacts_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "organizations_id", referencedColumnName = "id"))
private Set<Organization> organizations = new HashSet<>();
In the Organization class i have
#ManyToMany(mappedBy = "organizations")
private Set<Contact> contacts = new HashSet<>();
and in the organisationDTO class i have
private Set<Long> contactsId = new HashSet<>();
In the mapping class i did the mapping this way but it doesn't seem to be working
#Mapper(componentModel = "spring", uses = { ContactMapper.class }))
public interface OrganizationMapper extends EntityMapper<OrganizationDTO, Organization> {
#Mapping(source = "contacts", target = "contactsId")
OrganizationDTO toDto(Organization organization);
#Mapping(source = "contactsId", target = "contacts")
Organization toEntity(OrganizationDTO organizationDTO);
default Organization fromId(Long id) {
if (id == null) {
return null;
}
Organization organization = new Organization();
organization.setId(id);
return organization;
}
default Long fromContact(Contact contact) {
return contact == null ? null : contact.getId();
}
}
#Mapper(componentModel = "spring", uses = { OrganizationMapper.class })
public interface ContactMapper extends EntityMapper<ContactDTO, Contact> {
ContactDTO toDto(Contact contact);
Contact toEntity(ContactDTO contactDTO);
default Contact fromId(Long id) {
if (id == null) {
return null;
}
Contact contact = new Contact();
contact.setId(id);
return contact;
}
}
the problem here it shows me the id and the label is null, and in the data base it adds the organization but does not add the contacts
Organization organization = organizationMapper.toEntity(organizationDTO);
for(Contact item : organization.getContacts()) {
log.info("******************************************" + item.getId());
log.info("++++++++++++++++++++++++++++++++++++++++++" + item.getLabel());
}
organization = organizationRepository.save(organization);

This currently is not supported. You are trying to map a property id from the list contacts into the contactsId list.
In order to achieve what you are looking forward you need to provide a way of mapping from Contact to Long.
Your mapper can look like:
#Mapper
public interface MyMapper {
#Mapping(source = "contacts", target = "contactsId")
OrganizationDTO toDto(Organization organization);
default Long fromContact(Contact contact) {
return contact == null ? null : contact.getId();
}
}

Related

What is the best way to save jena Result set in database?

I am creating a Spring web application that queries SPARQL endpoints. As a requirement, I'm supposed to save the query and the result for later viewing and editing. So far I have created some entities (QueryInfo, Result, Endpoint) that I use to save the information entered about the Query and the Result. However I'm having trouble with saving the actual results themselves
public static List<String> getSelectQueryResult(QueryInfo queryInfo){
Endpoint endpoint = queryInfo.getEndpoint();
Query query = QueryFactory.create(queryInfo.getContent());
List<String> subjectStrings = query.getResultVars();
List<String> list = new ArrayList();
RDFConnection conn = RDFConnectionFactory.connect(endpoint.getUrl());
QueryExecution qExec = conn.query(queryInfo.getContent()) ; //SELECT DISTINCT ?s where { [] a ?s } LIMIT 100
ResultSet rs = qExec.execSelect() ;
while (rs.hasNext()) {
QuerySolution qs = rs.next();
System.out.println("qs: "+qs);
RDFNode rn = qs.get(subjectStrings.get(0)) ;
System.out.print(qs.varNames());
if(rn!= null) {
if (rn.isLiteral()) {
Literal literal = qs.getLiteral(subjectStrings.get(0));
list.add(literal.toString());
} else if (rn.isURIResource()) {
Resource subject = qs.getResource(subjectStrings.get(0));
System.out.println("Subject: " + subject.toString());
list.add(subject.toString());
}
}
}
return list;
}
My Result entity looks like this:
#Entity #Data #Table(schema = "sparql_tool") public class Result {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(length = 10485760)
private String content;
#OneToOne
#JoinColumn(name = "query_info_id",referencedColumnName = "id")
private QueryInfo queryInfo;
#Column(length = 10485760)
#Convert(converter = StringListConverter.class)
private List<String> contentList;
public Result() {
}
public Result(String content, QueryInfo queryInfo, List<String> list) {
this.content = content;
this.queryInfo = queryInfo;
this.contentList=list;
}
}
I used to save the actual results in the List contentList attribute. However, this only works when the query has only one result variable. If I have multiple result variables I have a table instead of a list. What is the best way to save this result in DB?
I'm working with an SQL DB if that is relevant. Thank you so much in advance!

Hibernate search : Sorting with filter on nested object, how to?

I have to code an hibernate search query (for elastic search database backend) which include a conditionnal sort of this kind :
Date dateOfBirth = new Date('01/01/2000');
Integer age = 10;
if (dateOfBirth == null) {
//then sort by age
}
else {
//sort by date of birth
}
I found an example to code this conditionnal sort inside Hibernate Search Reference, it can be done like this (quoted example) :
List<Author> hits = searchSession.search( Author.class )
.where( f -> f.matchAll() )
.sort( f -> f.field( "books.pageCount" )
.mode( SortMode.AVG )
.filter( pf -> pf.match().field( "books.genre" )
.matching( Genre.CRIME_FICTION ) ) )
.fetchHits( 20 );
My problem is that I hibernate search throws an exception at runtime. My sort filter code :
case DATE_SIGNATURE:
FieldSortOptionsStep bivSortFirst = f.field(Depot_.VENTE + "." + Vente_.DATE_SIGNATURE)
.filter(fa ->
{
PredicateFinalStep a = fa.bool(bo -> bo.must(fa.exists().field(Depot_.VENTE + "." + Vente_.DATE_SIGNATURE)));
return fa.bool(b0 -> b0.must(a));
}
);
FieldSortOptionsStep bivSortSecond = f.field(Depot_.VENTE + "." + Vente_.ACTE + "." + Acte_.SIGNATURE)
.filter(fa ->
{
PredicateFinalStep a = fa.bool(bo -> bo.mustNot(fa.exists().field(Depot_.VENTE + "." + Vente_.DATE_SIGNATURE)));
PredicateFinalStep b = fa.bool(bo -> bo.must(fa.exists().field(Depot_.VENTE + "." + Vente_.ACTE + "." + Acte_.SIGNATURE)));
return fa.bool(b0 -> b0.must(a).must(b));
}
);
sortFieldOrderedList.add(bivSortFirst);
sortFieldOrderedList.add(bivSortSecond);
break;
In the above example, I sort on two fields by priority. The first is assimilable to 'date of birth' and the second to 'age'. At runtime, the filter are not accepted by hibernate search and then throws an exception like follows :
The error message :
HSEARCH400604: Invalid sort filter: field 'vente.acte.signature' is
not contained in a nested object. Sort filters are only available if
the field to sort on is contained in a nested object. Context: field
'vente.acte.signature'
I read to do so, I need to go for 'inner_hits' query for elastic search. But how do I do this with hibernate search API ?
Thanks.
EDIT : Hibernate mapping of classes :
#Entity
#Indexed
public class Depot {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "vente_fk")
protected Vente vente;
#IndexedEmbedded(includePaths = {
Vente_.ID,
Vente_.DATE_SIGNATURE,
Vente_.DATE_SIGNATURE_ACTE,
Vente_.ACTE + "." + Acte_.SIGNATURE,
and much more
}
public Vente getVente() {
return this.vente;
}
...
}
#Entity
public class Vente {
#OneToMany(mappedBy = Depot_.VENTE, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
protected Set<Depot> depot = new HashSet<>();
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "acte_fk")
protected Acte acte;
...
#AssociationInverseSide(inversePath = #ObjectPath(#PropertyValue(propertyName = Acte_.VENTE)))
#IndexedEmbedded
public Acte getActe() {
return this.acte;
}
...
}
#Entity
public class Acte {
...
#GenericField(projectable = Projectable.YES, sortable = Sortable.YES, aggregable = Aggregable.YES)
protected Date signature;
#OneToMany(mappedBy = Vente_.ACTE)
protected Set<Vente> vente = new HashSet<>();
public Date getSignature() {
return this.signature;
}
...
}
From what I can see, for each Depot, there is at most one Acte and one Vente. So what you're trying to do is a bit exotic, as filtering in sorts is generally used on multi-valued nested objects.
The reason it's not working is you didn't mark the #IndexedEmbedded objects (vente, acte) as "nested"; as explained in the documentation, filtering only works on nested objects. And "nested" has a very precise meaning, it's not synonmymous with "indexed-embedded".
However, I think the whole approach is wrong in this case: you shouldn't use filtering. I'm quite sure that even if you mark the #IndexedEmbedded objects as "nested", you will face other problems, because what you're trying to do isn't the intended purpose of filtering. One of those problems could be performance; nested documents mean runtime joins, and runtime joins aren't cheap.
Instead, consider solving this problem at indexing time. Instead of trying to figure out which date to use for each document when searching, do that when indexing:
#Entity
#Indexed
public class Depot {
//...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "vente_fk")
protected Vente vente;
#IndexedEmbedded(includePaths = {
Vente_.ID,
Vente_.DATE_FOR_SORT, // <================= ADD THIS
Vente_.DATE_SIGNATURE,
Vente_.DATE_SIGNATURE_ACTE,
Vente_.ACTE + "." + Acte_.SIGNATURE,
//and much more
})
public Vente getVente() {
return this.vente;
}
}
#Entity
public class Vente {
#OneToMany(mappedBy = Depot_.VENTE, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
protected Set<Depot> depot = new HashSet<>();
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "acte_fk")
protected Acte acte;
//...
#AssociationInverseSide(inversePath = #ObjectPath(#PropertyValue(propertyName = Acte_.VENTE)))
#IndexedEmbedded
public Acte getActe() {
return this.acte;
}
// v================= ADD THIS
#Transient
#IndexingDependency(derivedFrom = {
#ObjectPath(#PropertyValue(propertyName = Vente_.DATE_SIGNATURE)),
#ObjectPath(#PropertyValue(propertyName = Vente_.ACTE), #PropertyValue(propertyName = Acte_.SIGNATURE)),
})
public Date getDateForSort() {
if ( getDateSignature() != null ) {
return getDateSignature();
}
else {
return getActe().getSignature();
}
}
// ^================= ADD THIS
//...
}

Bidirectional #OneToMany and #ManyToOne - what`s the right way to remove child from collection without removing from database

Suppose I have two mapped entities, Field and Cluster. I would like to remove from Field one of mapped Clusters without deleting this Cluster from database. What`s the best way to do it?
Field.java
#Entity
#Table(name = "field")
public class Field extends Base {
...
#OneToMany(mappedBy = "field", fetch = FetchType.LAZY)
private List<Cluster> clusters = new ArrayList<>();
Cluster.java
#Entity
#Table(name = "cluster")
public class Cluster extends Base {
....
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "field_id")
private Field field;
Now I have to do something like that:
public FieldOutDto save(FieldInDto createRequest) {
Field newField = new Field();
modelMapper.map(createRequest, newField);
setClusters(newField, createRequest);
return modelMapper.map(fieldRepository.save(newField), FieldOutDto.class);
}
public FieldOutDto update(String id, FieldInDto updateRequest) {
Field fromDb = fieldRepository.findById(UUID.fromString(id)).orElseThrow(EntityNotFoundException::new);
modelMapper.map(updateRequest, fromDb);
clusterRepository.findAllByField_Id(UUID.fromString(id)).forEach(cluster -> cluster.setField(null));
setClusters(fromDb, updateRequest);
return modelMapper.map(fieldRepository.save(fromDb), FieldOutDto.class);
}
public void delete(String id) {
findById(id).getClusters()
.forEach(cluster -> cluster.setField(null));
fieldRepository.deleteById(UUID.fromString(id));
}
private void setClusters(Field field, FieldInDto request) {
if (request.getClustersUuid() != null && !request.getClustersUuid().isEmpty()) {
field.setClusters(request.getClustersUuid().stream()
.map(id -> {
Cluster cluster = clusterRepository.findById(UUID.fromString(id)).orElseThrow(EntityNotFoundException::new);
cluster.setField(field);
return cluster;
})
.collect(Collectors.toList()));
} else {
field.setClusters(Collections.EMPTY_LIST);
}
}
I try to find a way to get the same result without it in UPDATE
clusterRepository.findAllByField_Id(UUID.fromString(id)).forEach(cluster -> cluster.setField(null));
and in DELETE methods
findById(id).getClusters()
.forEach(cluster -> cluster.setField(null));
One of the ways to remove a child from collection without removing from the database is by creating utility methods to add or remove children(Clusters) in your Parent(Field) class as below.
#Entity
#Table(name = "field")
public class Field extends Base {
...
#OneToMany(mappedBy = "field", fetch = FetchType.LAZY)
private List<Cluster> clusters = new ArrayList<>();
public void addCluster(Cluster cluster) {
cluster.add(cluster);
cluster.setField(this);
}
public void removeCluster(Cluster cluster) {
cluster.remove(cluster);
cluster.setField(null);
}
}
Then you call these methods to add or remove a child(cluster) from the parent(Field) without deleting it from DB in advance.

Mapstruct ignore method generation

Is there a way to ignore the generation of the mapper for the 3rd method in this code sample using mapstruct?
#Mapper(unmappedSourcePolicy = ReportingPolicy.IGNORE, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface EmployeeMapper {
EmployeeMapper MAPPER = Mappers.getMapper( EmployeeMapper.class );
#Mapping(source = "id", target = "id")
#Mapping(source = "firstName", target = "firstname")
#Mapping(source = "surname", target = "surname")
#Mapping(source = "employmentses", target = "employmentDTOList")
EmployeeDTO employee2dto(Employees employees);
#Mapping(source = "id", target = "id")
#Mapping(source = "firstName", target = "firstname")
#Mapping(source = "surname", target = "surname")
#Mapping(target = "employmentDTOList", ignore = true)
EmployeeDTO domainView2dto(EmployeeView employeeView);
//to be ignored by Mapstruct
EmployeePageDTO domainPage2dto(Page<EmployeeView> employeeViewPage);
}
You can simply define a default method inside the interface as stated here:
#Mapper(unmappedSourcePolicy = ReportingPolicy.IGNORE, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface EmployeeMapper {
EmployeeMapper MAPPER = Mappers.getMapper( EmployeeMapper.class );
//.....
//to be ignored by Mapstruct
default EmployeePageDTO domainPage2dto(Page<EmployeeView> employeeViewPage) {
//.... insert body here
}
}

Expose enums with Spring Data REST

I'm using Spring Boot 1.5.3, Spring Data REST, HATEOAS.
I've a simple entity model:
#Entity
public class User extends AbstractEntity implements UserDetails {
private static final long serialVersionUID = 5745401123028683585L;
public static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();
#NotNull(message = "The name of the user cannot be blank")
#Column(nullable = false)
private String name;
/** CONTACT INFORMATION **/
private String landlinePhone;
private String mobilePhone;
#NotNull(message = "The username cannot be blank")
#Column(nullable = false, unique = true)
private String username;
#Email(message = "The email address is not valid")
private String email;
#JsonIgnore
private String password;
#Column(nullable = false)
private String timeZone = "Europe/Rome";
#JsonIgnore
private LocalDateTime lastPasswordResetDate;
#Column(nullable = false, columnDefinition = "BOOLEAN default true")
private boolean enabled = true;
#Type(type = "json")
#Column(columnDefinition = "json")
private Roles[] roles = new Roles[] {};
and my enum Roles is:
public enum Roles {
ROLE_ADMIN, ROLE_USER, ROLE_MANAGER, ROLE_TECH;
#JsonCreator
public static Roles create(String value) {
if (value == null) {
throw new IllegalArgumentException();
}
for (Roles v : values()) {
if (value.equals(v.toString())) {
return v;
}
}
throw new IllegalArgumentException();
}
}
I'm creating a client in Angular 4. Spring Data REST is great and expose repository easily return my model HATEOAS compliant:
{
"_embedded": {
"users": [
{
"name": "Administrator",
"username": "admin",
"roles": [
"Amministratore"
],
"activeWorkSession": "",
"_links": {
"self": {
"href": "http://localhost:8080/api/v1/users/1"
},
"user": {
"href": "http://localhost:8080/api/v1/users/1{?projection}",
"templated": true
}
}
},
Like you can see I'm also translating via rest-messages.properties the value of my enums. Great!
My Angular page now needs the complete lists of roles (enums). I've some question:
understand the better way for the server to return the list of roles
how to return this list
My first attemp was to create a RepositoryRestController in order to take advantage of what Spring Data REST offers.
#RepositoryRestController
#RequestMapping(path = "/api/v1")
public class UserController {
#Autowired
private EntityLinks entityLinks;
#RequestMapping(method = RequestMethod.GET, path = "/users/roles", produces = "application/json")
public Resource<Roles> findRoles() {
Resource<Roles> resource = new Resource<>(Roles.ROLE_ADMIN);
return resource;
}
Unfortunately, for some reason, the call to this methods return a 404 error. I debugged and the resource is created correctly, so I guess the problem is somewhere in the JSON conversion.
how to return this list?
#RepositoryRestController
#RequestMapping("/roles")
public class RoleController {
#GetMapping
public ResponseEntity<?> getAllRoles() {
List<Resource<Roles>> content = new ArrayList<>();
content.addAll(Arrays.asList(
new Resource<>(Roles.ROLE1 /*, Optional Links */),
new Resource<>(Roles.ROLE2 /*, Optional Links */)));
return ResponseEntity.ok(new Resources<>(content /*, Optional Links */));
}
}
I was playing around with this and have found a couple of ways to do it.
Assume you have a front end form that wants to display a combo box containing priorities for a single Todo such as High, Medium, Low. The form needs to know the primary key or id which is the enum value in this instance and the value should be the readable formatted value the combo box should display.
If you wish to customize the json response in 1 place only such as a single endpoint then I found this useful. The secret sauce is using the value object PriorityValue to allow you to rename the json field through #Relation.
public enum Priority {
HIGH("High"),
NORMAL("Normal"),
LOW("Low");
private final String description;
Priority(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public static List<Priority> orderedValues = new ArrayList<>();
static {
orderedValues.addAll(Arrays.asList(Priority.values()));
}
}
#RepositoryRestController
#RequestMapping(value="/")
public class PriorityController {
#Relation(collectionRelation = "priorities")
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
private class PriorityValue {
private String id;
private String value;
public PriorityValue(String id,
String value) {
this.id = id;
this.value = value;
}
}
#GetMapping(value = "/api/priorities", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity<Resources<PriorityValue>> getPriorities() {
List<PriorityValue> priorities = Priority.orderedValues.stream()
.map(p -> new PriorityValue(p.name(), p.getDescription()))
.collect(Collectors.toList());
Resources<PriorityValue> resources = new Resources<>(priorities);
resources.add(linkTo(methodOn(PriorityController.class).getPriorities()).withSelfRel());
return ResponseEntity.ok(resources);
}
}
Another approach is to use a custom JsonSerializer. The only issue using this is everywhere a Priority enum is serialized you will end up using this format which may not be what you want.
#JsonSerialize(using = PrioritySerializer.class)
#Relation(collectionRelation = "priorities")
public enum Priority {
HIGH("High"),
NORMAL("Normal"),
LOW("Low");
private final String description;
Priority(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public static List<Priority> orderedValues = new ArrayList<>();
static {
orderedValues.addAll(Arrays.asList(Priority.values()));
}
}
#RepositoryRestController
#RequestMapping(value="/api")
public class PriorityController {
#GetMapping(value = "/priorities", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity<Resources<Priority>> getPriorities() {
Resources<Priority> resources = new Resources<>(Priority.orderedValues);
resources.add(linkTo(methodOn(PriorityController.class).getPriorities()).withSelfRel());
return ResponseEntity.ok(resources);
}
}
public class PrioritySerializer extends JsonSerializer<Priority> {
#Override
public void serialize(Priority priority,
JsonGenerator generator,
SerializerProvider serializerProvider)
throws IOException, JsonProcessingException {
generator.writeStartObject();
generator.writeFieldName("id");
generator.writeString(priority.name());
generator.writeFieldName("value");
generator.writeString(priority.getDescription());
generator.writeEndObject();
}
}
The final json response from http://localhost:8080/api/priorities
{
"_embedded": {
"priorities": [
{
"id": "HIGH",
"value": "High"
},
{
"id": "NORMAL",
"value": "Normal"
},
{
"id": "LOW",
"value": "Low"
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/api/priorities"
}
}
}

Resources