How to cast Jest SearchResult hits to domain object - java

I am using Jest to query Elasticsearch and so far it has been great. Jest's documentation says:
Result can be cast to List of domain object;
... and shows this example:
SearchResult result = client.execute(search);
List<SearchResult.Hit<Article, Void>> hits = searchResult.getHits(Article.class);
// or
List<Article> articles = result.getSourceAsObjectList(Article.class);
getSourceAsObjectList is deprecated, and I am using:
List<SearchResult.Hit<ImmutableConceptDocument, Void>> concepts = result.getHits(ImmutableConceptDocument.class);
... Where ImmutableConceptDocument is an immutables generated class - otherwise pretty straight forward POJO, with attributes named as I see under the source of my search results.
However, when I use the above line, I don't get the source properties mapped, I do get other details like score, type, index etc. mapped.
What am I missing? Does the domain class need to have specific Jest annotations or something like that?
I can't see any good examples in the unit tests too. This one maps to Object.class and that does not show me a mapping example.
Here is the immutable class:
#Value.Immutable
public abstract class EsConceptDocument {
public abstract String term();
public abstract Category type();
public abstract List<String> synonyms();
}
... where Category is an enum type.

As Val pointed out in the comments, this was because immutables.io makes the generated class' constructor private (and exposes a builder).
I removed immutable from this class and wrote a constructor and getters and it worked.

Related

How to customize the API metadata of springdoc-openai?

I'm trying to customize the springdoc-openapi, make it can work with my framework, but I meet two problems.
1. How to treat methods that do not start with is/get as properties of Model?
If users use my ORM framework by Java language, the property getters in the entity interface can either start with is/get like a traditional Java Bean, or don't start with is/get like a Java record, for example
#Entity
public interface Book {
#Id
long id();
String name();
int edition();
BigDecimal price();
#ManyToOne
BookStore store();
#ManyToMany
List<Author> authors();
}
Here, the wording that does not start with is/get is used, which looks like a java record, not a traditional java bean.
However, doing this will cause swagger-ui to think that the model doesn't have any attributes. So I have to change the behavior of swagger.
After some research, I found that this behavior can be changed using io.swagger.v3.core.converter.ModelConverter, which is the most likely solution.
However, springdoc-openapi does not explain in detail how to use ModelConverter in the documentation. Ultimately, this goal was not achieved.
2. How to control the shape of dynamic objects in HTTP response?
My ORM is GraphQL-style, its entity objects are dynamic so that data structures of arbitrary shapes can be queried, just like GraphQL does. For example
#RestController
public class BookController {
#AutoWired
private JSqlClient sqlClient;
// Query simple book objects
#GetMapping("/books")
public List<Book> books() {
return sqlClient.getEntities().findAll(Book.class);
}
// Query complex book objects
#GetMapping("/books/details")
public List<Book> bookDetails() {
return sqlClient.getEntities().findAll(
// Like the request body of GraphQL
BookFetcher$
.allScalarFields()
.store(
BookStoreFetcher.$.allScalarFields()
)
.authors(
AuthorFetcher.$.allScalars()
)
);
}
}
The first query returns a list of simple book objects in the format {id, name, edition, price}
The second query returns a list of complex book objects in the format {id, name, edition, price, store: {id, name, website}, authors: {id, firstName, lastName, gender}}
Dynamic objects can vary in shape, and these are just two special cases.
I expect swgger to tell the client the shape of the object returned by each business scenario. So, I defined an annotation called #FetchBy. It should be used like this
#RestController
public class BookController {
private static final Fetcher<Book> BOOK_DETAIL_FETCHER =
BookFetcher$
.allScalarFields()
.store(
BookStoreFetcher.$.allScalarFields()
)
.authors(
AuthorFetcher.$.allScalars()
);
#AutoWired
private JSqlClient sqlClient;
#GetMapping("/books")
public List<Book> books() {
return sqlClient.getEntities().findAll(Book.class);
}
#GetMapping("/books/details")
public List<#FetchBy("BOOK_DETAIL_FETCHER") Book> bookDetails() {
return sqlClient.getEntities().findAll(BOOK_DETAIL_FETCHER);
}
}
Declare the shape of the complex object as a static constant.
The #FetchBy annotation uses the constant name to tell swgger the shape of the returned dynamic object.
After some research, I found that this behavior can be changed using org.springdoc.core.customizers.OperationCustomizer, which is the most likely solution.
However, I found that the schema tree of swagger is not consistent with the generic type definition tree in the java language. For example, Spring's ResponseEntity<> wrapper will be ignored by swagger and will be not parsed as a node of schema tree. Theoretically speaking, this ability of swagger can be customized infinitely, so the two trees may not always be consistent and difficult to analyze.

Is there any way to avoid using raw string usage in java map-struct?

I want to avoid the human mistake in mapping objects together so I use the map-struct package. but there are situations I should manually assign fields like renaming them. like this method
#Mapper(componentModel = "spring")
public interface ItemMapper extends EntityMapper<ItemDTO, Item> {
#Mapping(target = "itemTeplate", source = "template")
Item toEntity(Entity entity);
}
Is there any way to generate the class field names dynamically for this usage and get an error when changing the naming and have an autocomplete class like fields? like below picture
#Mapper(componentModel = "spring")
public interface ItemMapper extends EntityMapper<ItemDTO, Item> {
#Mapping(target = EntityFields.ITEM_TEMPLATE, source = ItemFields.TEMPLATE)
Item toEntity(Entity entity);
}
MapStruct is an annotation processor and code generator which internals work based on the reflection. The reflection-based look-up for the fields and getters/setters is based on the String matching.
I fully understand your worries, however, consider these facts:
Replacing such string literal with enumeration doesn't help you much and adds only an additional layer. As long as the object field is changed, the enumeration has to be changed as well. To be the devil's advocate, I admit you might want to use it anyway in case of a lot of similar mappings - however, for that I remind you that the mappings can be inherited.
On compilation, MapStruct correctly throws a warning when a field is unmapped, which might happen when a new field is added. If the field is not found at all, i.e. the field is either modified or removed, the compilation fails. MapStruct follows the fail-fast principle.

Remove attribute from object

I got a list that return from below db call.
List<employee> list = empolyeeRepository.findByEmployeeId(id);
List contains employee pojo class object. I want to remove one attribute let's say "employee bank account no" when returning from rest call.
#RequestMapping(value = "/employeeInformation/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public List<Employee> getEmployeeInformation(#PathVariable("id") String id) throws Exception {
return empolyeeRepository.findByEmployeeId(id);
}
Is there any annotation or good practice to do that?
As it mentioned in comments above, you cant remove fields of compiled class at runtime. Assuming you have to exclude some field from generated json, there I see two options:
Create a class with fields you want to be present in resulting json, copy required values from original object to a new created. This approach is called view model and allows you to decorate some object's data, hiding sensitive data from being exposed.
Depending on implementation of your serializer there may be annotations to exclude fields. #JsonIgnore may be placed on getter method, if you are using Jackson (default in spring boot). Second aproach requires significant less code, but the first one is more flexible.
Try #JsonIgnore to ignore properties from serialization and de-serialization.
Here is the link to the docs

Jersey Marshalling Member List Issue

I'm using Java Jersey 1.x to marshall an object that has several members, one of which is a list. All member variables are getting marshalled properly and returned with the correct return type. However, it doesn't include the objectList in the return data.
Example:
#XmlRootElement
public class ClassWithList {
private String front;
private int total;
private ArrayList<AnotherPOJOObject> objectList;
...
getters/setters
Getter:
public List<AnotherPOJOObject> getObjectList() {
return objectList;
}
I debugged it and checked that objectList is indeed populated with data.
AnotherPOJOObject is also annotated as an XmlRootElement
Have a look at http://docs.oracle.com/javase/6/docs/api/javax/xml/bind/annotation/XmlAccessorType.html . It details how JAXB will attempt to serialize POJOs. In particular note that it defaults to public members only - which means that "Every public getter/setter pair and every public field will be automatically bound to XML, unless annotated by XmlTransient". In this case I'm guessing that you don't have a public setter field for objectList so JAXB won't serialize it. To get the list to serialize you could:
Add a public setter method for objectList
Declare objectList as public (probably not a good idea)
Add an #XmlElement annotation to the getter to explicitly tell JAXB to marshal the list to XML.
I had faced the same issue and solved after some trial and error.
Try giving the annotation #XmlElementWrapper(name = "orders") to getObjectList() and also make the type to private List<AnotherPOJOObject> objectList;
Thanks to the suggestion to basiljames, I was able to get closer to the answer. The real issue was that the List of AnotherPOJOOject wasn't so plain after all. Each object had an untyped Map of it's own, and that threw the Marshaller into a fit, because, it always wants to know the type of an object.
I guess the takeaway from this answer to make sure that every collection you return has a well defined type!

Is it possible to write web-service that returns a collection of generic type? Spring 3

In my db I have a number of entity classes and I run standart CRUD operations on them via Hibernate. Its not a problem to create generic dao class to make all main operations with classes. For example, in dao I have methods which look like this:
<T> List<T> loadAll(Class clazz)
Now I want to expose these methods to web-service client via Spring 3 operated web-service.
The only way I see is to implement web-methods for all entities i.e. write a class that looks like...
class BookResponse { List<BookEntity> books; }
... and return this in corresponding web-method "BookResponse getAllBooks()". This will ruin my attemts to make a code simplier by using dao with generics.
Is there are any other ways?
How can I do this without implementing web-methods for ALL my entities?
If generic web-service is not possible may be there are some other ways to resolve this task in a simple way?
UPDATE:
At the moment I am trying to implement a response class which should look like
public class ServiceResponse<T>{
#XmlElementWrapper( name = "data" )
#XmlElements( #XmlElement(name = "a", type = EntityA.class), #XmlElement(name = "b", type = EntityB.class) )
private List<T> data = new ArrayList<T>( );
//getters,setters
}
So I want to be able to insert a list of any entities mapped with annotations to this response. This produces no erros, but the response given me by web-service is empty.
I think you'll need a new POJO "GenericEntity" which can hold the information of any domain entity class instance.
It would hold a type string and an arbitrary/generic list of named attributes.
It can then be used to represent any of your real domain entities
e.g.
type = Book
attributes = (title=Order of the Phoenix, author=J K Rowling)
e.g.
type = Car
attributes = (make=Renault, model=Clio)
These examples show String attributes so you'll have to sort out if this is good enough or if you need strong typing - it's possible but harder.
You can then expose your "GenericEntity" via web services, allowing clients to make calls in and specify which domain entity they wish to search for, and even allow them to specify search criteria too.
Adds and deletes could be done in a similar way.
HTH,
David

Categories

Resources