Hibernate Search 6 combine projections not working - spring-boot

I have implemented Hibernate Search and am currently having issues with Projection.
All relevant data are indexed and accordingly I try to project them to the DTO.
as the documentation (https://docs.jboss.org/hibernate/stable/search/reference/en-US/html_single/#search-dsl-projection-composite), i have tried the following
searchSession.search(Building.class)
.select(f -> f.composite(BuildingDto::new,
f.field("id", String.class),
f.field("name", String.class),
f.field("street", String.class),
f.field("zip", String.class),
f.field("town", String.class)))
.where(f -> f.wildcard().fields("id", "name", "town", "street", "zip").matching(search))
.sort(f -> f.field("id").desc())
.fetch(20);
While running the build I get following error-message:
java: method composite in interface org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory<R,E> cannot be applied to given types;
required: org.hibernate.search.engine.search.projection.dsl.ProjectionFinalStep<?>[]
found: BuildingDto::new,org.hibernate.search.engine.search.projection.dsl.FieldProjectionValueStep<capture#1 of ?,java.lang.String>,org.hibernate.search.engine.search.projection.dsl.FieldProjectionValueStep<capture#2 of ?,java.lang.String>,org.hibernate.search.engine.search.projection.dsl.FieldProjectionValueStep<capture#3 of ?,java.lang.String>,org.hibernate.search.engine.search.projection.dsl.FieldProjectionValueStep<capture#4 of ?,java.lang.String>,org.hibernate.search.engine.search.projection.dsl.FieldProjectionValueStep<capture#5 of ?,java.lang.String>
reason: varargs mismatch; bad return type in method reference
BuildingDto cannot be converted to org.hibernate.search.engine.search.projection.SearchProjection<java.lang.Object>
BuildingDto has the following constructors:
public BuildingDto (){}
public BuildingDto (String id, String name,String street, String zip, String town) {
//setting vars
}

EDIT: Starting with Hibernate Search 6.2, you can annotate your DTO contructor with #ProjectionConstructor:
#ProjectionConstructor
public BuildingDto (String id, String name,String street, String zip, String town) {
//setting vars
}
Make sure to compile your code with the -parameters compiler flag.
Then, you can simply do this:
searchSession.search(Building.class)
.select(BuildingDto.class)
.where(f -> f.wildcard().fields("id", "name", "town", "street", "zip").matching(search))
.sort(f -> f.field("id").desc())
.fetch(20);
More information here.
Original answer (Hibernate Search 6.1 and below):
As explained in the reference documentation, type-safe composite projections are only supported up to three inner projections at the moment.
If you need more, your "transformer" function that creates the DTO will need to accept a List<?> and do some casts:
searchSession.search(Building.class)
.select(f -> f.composite(list -> new BuildingDto(
(String) list.get(0),
(String) list.get(1),
(String) list.get(2),
(String) list.get(3),
(String) list.get(4)
),
f.field("id", String.class),
f.field("name", String.class),
f.field("street", String.class),
f.field("zip", String.class),
f.field("town", String.class)))
.where(f -> f.wildcard().fields("id", "name", "town", "street", "zip").matching(search))
.sort(f -> f.field("id").desc())
.fetch(20);
Yes that's ugly, but we're working on better solutions.

Related

Spring Data JPA projection issue with field starting with IS in kotlin

I'm using interface based projection to get certain fields from db. one of my field name start with is. I'm able to get the field from database via native query, however, the response returned by spring boot controller does not contain is in field name. How should I resolve it?
interface UserProjection {
val userId: Long
val isPrivate: Boolean
val likesCount: Int
}
Query
SELECT u.user_id as userId, u.is_private as private, u.likes_count as likesCount FROM users u WHERE u.user_id=?;
However, response returned by spring boot is
{
"userId": 12345,
"private": false,
"likesCount": 1
}
The solution is to use a fun to get the field instead of a val
interface LoginUserProjection {
val id: Long
val passwordHash: String
fun getIsVerifiedAccount(): Boolean
}

Javax validation of generics in Springboot with Kotlin

I have a controller:
#PostMapping
fun create(
#RequestBody #Valid request: MyContainer<CreateRequest>,
): MyContainer<Dto> = service.create(request.objects)
with MyContainer and CreateRequest looking something like this:
class MyContainer<T>(
#field:Valid // also tried param
#field:NotEmpty(message = "The list of objects can not be null or empty")
var objects: List<#Valid T>? = listOf(),
)
class CreateRequest(
#field:NotNull(message = "Value can not be null")
var value: BigDecimal? = null,
)
In my tests, the "outer" validation works, that is I do get the expected error message if I send it { "objects": null } or { "objects": [] }. But I can not get it to validate the contents of the list. From what I understand in Java List<#Valid T> should work, but for whatever I can not get it to work in kotlin.
I figured I might need some kind of use-site target on #Valid in List<#Valid T>, but I can't find one that's applicable for this use case.
How can I get the validation to work for the list?
I managed to find a solution myself.
Apparently get: is the correct use-site target, not field: or param:. Furthermore the #Valid in List<#Valid T> was not necessary.
For reference, here's the working class (also changed it back to a data class as that doesn't seem to pose an issue).
class MyContainer<T>(
#get:Valid
#get:NotEmpty(message = "The list of objects can not be null or empty")
var objects: List<T>? = listOf(),
)
and the CreateRequest:
class CreateRequest(
#get:NotNull(message = "Value can not be null")
var value: BigDecimal? = null,
)
Changing to the get: use-site target was only necessary for #Valid, but I opted for using it everywhere for consistency and since it seems to be the one that works best.

GraphQL register new Type wiring with argument to data fetcher

Having a GraphQL schema:
type TypeA {
id: ID,
name: String,
other: TypeC
}
type TypeB {
id: ID,
name: String,
other: TypeC
}
How should I implement TypeC wiring independently from source object type? I know I can do:
RuntimeWiring.newRuntimeWiring()
.type(TypeRuntimeWiring.newTypeWiring("TypeA")
.dataFetcher("other", dataFetcher_typeC.get())
.type(TypeRuntimeWiring.newTypeWiring("TypeB")
.dataFetcher("other", dataFetcher_typeC.get())
.build()
but then the data fetcher is dependant on a source object type:
DataFetcher<CompletableFuture<Collection<TypeC>>> get() {
return dataFetchingEnvironment -> {
<??> sourceObj = dataFetchingEnvironment.getSource();
return getObject(sourceObj.someProperty);
};
}
Given both POJOs (TypeA and TypeB) have reference field to TypeC, how to resolve TypeC field by given reference, not source object?
I have actually figured out two possible solutions to the problem:
When defining new wiring, get source object and from it the field. Call dataFetcher method with parameter, like regular java method:
Inside data fetcher get field name from DataFetcherEnvironment. Use reflection to get field from source object
Example #1:
RuntimeWiring.newRuntimeWiring()
.type(TypeRuntimeWiring.newTypeWiring("TypeA")
.dataFetcher("other", environment -> {
TypeA sourceObj = environment.getSource();
return dataFetcher_typeC.get(sourceObj.other)})
.type(TypeRuntimeWiring.newTypeWiring("TypeB")
TypeB sourceObj = environment.getSource();
return dataFetcher_typeC.get(sourceObj.other)})
.build()
Example #2:
DataFetcher<CompletableFuture<Collection<TypeC>>> get() {
return dataFetchingEnvironment -> {
Field declaredField = dataFetchingEnvironment.getSource().getClass()
.getDeclaredField(dataFetchingEnvironment.getField().getName());
declaredField.setAccessible(true);
String value = (String) declaredField.get(dataFetchingEnvironment.getSource());
return getObject(sourceObj.someProperty);
};
}
Second option looks better but still unsure if this is correct approach.
From the documentation here
the dataFetchingEnvironment provides getExecutionStepInfo() method which returns the ExecutionStepInfo object. From there, you can get the parent information.
ExecutionStepInfo executionStepInfo = environment.getExecutionStepInfo();
ExecutionStepInfo parentInfo = executionStepInfo.getParent();
GraphQLObjectType parentType = (GraphQLObjectType) parentInfo.getUnwrappedNonNullType();
// parentType.getName() returns you "TypeA" or "TypeB"

Create instance of SpringĀ“s ParameterizedTypeReference in Kotlin

I am trying to learn Kotlin, and test how it works with spring boot. My application is using a mongo database to store data and I have a Jersey resource for retrieving data. I am testing it using spring-boot-test and RestTestTemplate.
The RestTestTemplate has an exchange method which takes a ParameterizedTypeReference. This class has a protected constructor. So the only way I managed to use it from Kotlin was like this:
class ListOfPeople : ParameterizedTypeReference<List<Person>>()
Here is my test-method:
#Test
fun `get list of people`() {
// create testdata
datastore.save(Person(firstname = "test1", lastname = "lastname1"))
datastore.save(Person(firstname = "test2", lastname = "lastname2"))
datastore.save(Person(firstname = "test3", lastname = "lastname2"))
datastore.save(Person(firstname = "test4", lastname = "lastname2"))
val requestEntity = RequestEntity<Any>(HttpMethod.GET, URI.create("/person"))
// create typereference for response de-serialization
class ListOfPeople : ParameterizedTypeReference<List<Person>>() // can this be done inline in the exchange method?
val responseEntity : ResponseEntity<List<Person>> = restTemplate.exchange(requestEntity, ListOfPeople())
assertNotNull(responseEntity)
assertEquals(200, responseEntity.statusCodeValue)
assertTrue( responseEntity.body.size >= 4 )
responseEntity.body.forEach { person ->
println("Found person: [${person.firstname} ${person.lastname}] " +
", born [${person.birthdate}]")
}
}
Is this the correct (or only) way to do this, or is there a better way?
If it helps, here is a link for the whole test: testclass on github
While the answer using object expression is correct and the direct equivalent of the way you do it in Java, reified type parameters allow you to simplify it if you need many ParameterizedTypeReferences:
inline fun <reified T> typeReference() = object : ParameterizedTypeReference<T>() {}
// called as
restTemplate.exchange(requestEntity, typeReference<List<Person>>())
When the compiler sees a typeReference<SomeType> call, it's replaced by the definition, so the result is the same as if you wrote object : ParameterizedTypeReference<SomeType>() {}.
Thanks to JB Nizet who pointed me to the correct documentation.
val responseEntity : ResponseEntity<List<Person>> =
restTemplate.exchange(requestEntity,
object: ParameterizedTypeReference<List<Person>> () {})
If I read correctly this is called an Object expression.

GraphQL Schema Definition Language support in graphql-java

Hi I'm new to graphql specially graphql-java .
Is there any possible way to use GraphQL Schema Definition Language support in graphql-java? If yes then how it is possible?
Yes, it is possible since release 3.0.0.
schema {
query: QueryType
}
type QueryType {
hero(episode: Episode): Character
human(id : String) : Human
droid(id: ID!): Droid
}
You can convert an the following IDL-File (starWarsSchema.graphqls) to an executable schema like this:
SchemaParser schemaParser = new SchemaParser();
SchemaGenerator schemaGenerator = new SchemaGenerator();
File schemaFile = loadSchema("starWarsSchema.graphqls");
TypeDefinitionRegistry typeRegistry = schemaParser.parse(schemaFile);
RuntimeWiring wiring = buildRuntimeWiring();
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeRegistry, wiring);
Also you have to implement the buildRuntime function to wire your static schema with the resolvers.
RuntimeWiring buildRuntimeWiring() {
return RuntimeWiring.newRuntimeWiring()
.scalar(CustomScalar)
.type("QueryType", typeWiring -> typeWiring
.dataFetcher("hero", new StaticDataFetcher(StarWarsData.getArtoo()))
.dataFetcher("human", StarWarsData.getHumanDataFetcher())
.dataFetcher("droid", StarWarsData.getDroidDataFetcher())
)
.build();
}
A more detailed example you can find in the graphql-java documentation.

Resources