Use graphql-java-extended-scalars with Quarkus - java

I want to use ExtendedScalars.Json from the graphql-java-extended-scalars package in my application which utilizes the quarkus-smallrye-graphql however I am struggling doing so.
Given a Test model class like
public class Test {
public String name;
public Object description;
}
with following GraphQLApi
#GraphQLApi
public class API {
#Inject
SomeService someService;
#Query
public Test getTest() {
return someService.getTest();
}
}
where Test might be described by
{
"name": "Test",
"description": {
"hello": "world"
}
}
the field description should be treated as ExtendedScalars.Json, so the result of this GraphQL query
{
test {
description
}
}
should be exactly
{
"data": {
"test": {
"description": {
"hello": "world"
}
}
}
}
However the #ToScalar annotation does not take ExtendedScalars.Json and falling back to graphql-java with something like public GraphQLSchema.Builder addScalar(#Observes GraphQLSchema.Builder builder) { ... } to manually replace the Type and QueryType leads to a AssertException like
graphql.AssertException: All types within a GraphQL schema must have unique names. No two provided types may have the same name.
No provided type may have a name which conflicts with any built in types (including Scalar and Introspection types).
You have redefined the type 'Query' from being a 'GraphQLObjectType' to a 'GraphQLObjectType'
Any clue how to handle this?

Related

How to change field value in a javax.validation.ConstraintViolationException?

In a Quarkus (version 2.9.2.Final) application (not using Spring), with hibernate validation on a REST API, the objective is to validate the input parameters of a method. Everything works fine, except changing the class name to the one used in the api.
How can it be changed the class name on violations.field value of the exception to use the one used on the Rest API (defined in JsonTypeName?
The parameter class name:
#JsonTypeName("some_class")
public record SomeClass(
#NotNull #JsonProperty("timestamp_api") LocalDateTime timestamp) {
}
Resource class:
#Path("some_path")
public class SomeResource {
#POST
public Response send(#Valid SomeClass somePath) {
System.out.println("RECEIVED 2: " + somePath);
return Response.accepted().build();
}
}
Already implemented interface PropertyNodeNameProvider as indicated in Hibernate Validator documentation, but it only changes parameters of and not the class itself.
Calling the service with:
{}
Gives an error like:
{
"title": "Constraint Violation",
"status": 400,
"violations": [
{
"field": "send.someClass.timestamp_api",
"message": "must not be empty"
}
] }
The objective is to change someClass (internal name) to some_class (api name).

How to use WebMvcLinkBuilder with repository's findById?

I'm trying to use Spring Data Rest to implement a full set of services for about 60 entities. Right now, I'm getting by with just letting Spring use my repositories rather than implementing controllers, which is great!
The data I'm having to model isn't ideal--I'd prefer to have customerId come as part of the order object.
{
"tenantId": 42,
"id": "00000001",
"customer": {
"tenantId": 42,
"id": "CUST001",
"name": "Arthur Dent"
}
}
I have the ID for a related entity as a property on my JSON object.
public class Order {
Long tenantId;
String id;
String customerId;
}
I don't really want to pull the full Customer entity and all of the other related entities and place them as members on my Order object. Instead, I'd just like to add some links to the _links collection.
I believe I've figured out WebMvcLinkBuilder finally and I have the basic idea in place. However, JpaRepository.findById returns a java.util.Optional.
#Bean
public RepresentationModelProcessor<EntityModel<Order>> orderProcessor() {
return new RepresentationModelProcessor<EntityModel<Order>>() {
#Override
public EntityModel<Order> process(final EntityModel<Order> model) {
final CustomerRepository controller = WebMvcLinkBuilder.methodOn(CustomerRepository);
final CustomerId id = new CustomerId(model.getContent().getTenantId(), model.getContent().getCustomerId());
model.add(WebMvcLinkBuilder.linkTo(controller.findById(id)).withRel("customer"));
return model;
}
};
}
The error I get is:
Could not generate CGLIB subclass of class java.util.Optional: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class java.util.Optional
How can I add a link to my resource?

Jackson deserialization: Custom Object factory

How do you implement in Jackson a conversion from json to Java objects, based on class types specified in the json.
Example Java Types:
public class Car{
public String make;
public String model;
}
public class Spoon {
public String material;
}
public class Ownership {
public List<Object> items;
public User owner;
}
Example Json:
{
"items": [
{
"#class": "com.example.Car",
"make": "Mercedes-Benz",
"model": "S500"
},
{
"#class": "com.example.Spoon",
"material": "silver"
}
],
"owner": {
"name": "John"
}
}
Since the number of classes is unknown (users can add any class) it is not possible to use the annotation #JsonSubTypes.
In addition, the json may contain known strongly types classes, like the object User in the example which is serialized using the standard Jackson implementation.
Most of the examples I can find, such as http://www.baeldung.com/jackson-inheritance assume the number of subclasses is known, but in my case it is not, users of the framework will add their own.
Ideally the implementation will just resolve types and let Jackson do the rest of the serialization without repeating that code.
Can be solved using an annotation on the collection:
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "#class")
public List<Object> items;

How to use spring data with couchbase without _class attribute

Is there a simple way to use spring data couchbase with documents that do not have _class attribute?
In the couchbase I have something like this in my sampledata bucket:
{
"username" : "alice",
"created" : 1473292800000,
"data" : { "a": 1, "b" : "2"},
"type" : "mydata"
}
Now, is there any way to define mapping from this structure of document to Java object (note that _class attribute is missing and cannot be added) and vice versa so that I get all (or most) automagical features from spring couchbase data?
Something like:
If type field has value "mydata" use class MyData.java.
So when find is performed instead of automatically adding AND _class = "mydata" to generated query add AND type = "mydata".
Spring Data in general needs the _class field to know what to instantiate back when deserializing.
It's fairly easy in Spring Data Couchbase to use a different field name than _class, by overriding the typeKey() method in the AbsctractCouchbaseDataConfiguration.
But it'll still expect a fully qualified classname in there by default
Getting around that will require quite a bit more work:
You'll need to implement your own CouchbaseTypeMapper, following the model of DefaultCouchbaseTypeMapper. In the super(...) constructor, you'll need to provide an additional argument: a list of TypeInformationMapper. The default implementation doesn't explicitly provide one, so a SimpleTypeInformationMapper is used, which is the one that puts FQNs.
There's an alternative implementation that is configurable so you can alias specific classes to a shorter name via a Map: ConfigurableTypeInformationMapper...
So by putting a ConfigurableTypeInformationMapper with the alias you want for specific classes + a SimpleTypeInformationMapper after it in the list (for the case were you serialize a class that you didn't provide an alias for), you can achieve your goal.
The typeMapper is used within the MappingCouchbaseConverter, which you'll also need to extend unfortunately (just to instantiate your typeMapper instead of the default.
Once you have that, again override the configuration to return an instance of your custom MappingCouchbaseConverter that uses your custom CouchbaseTypeMapper (the mappingCouchbaseConverter() method).
You can achive this e.g. by creating custom annotation #DocumentType
#DocumentType("billing")
#Document
public class BillingRecordDocument {
String name;
// ...
}
Document will look like:
{
"type" : "billing"
"name" : "..."
}
Just create following classes:
Create custom AbstractReactiveCouchbaseConfiguration or AbstractCouchbaseConfiguration (depends which varian you use)
#Configuration
#EnableReactiveCouchbaseRepositories
public class CustomReactiveCouchbaseConfiguration extends AbstractReactiveCouchbaseConfiguration {
// implement abstract methods
// and configure custom mapping convereter
#Bean(name = BeanNames.COUCHBASE_MAPPING_CONVERTER)
public MappingCouchbaseConverter mappingCouchbaseConverter() throws Exception {
MappingCouchbaseConverter converter = new CustomMappingCouchbaseConverter(couchbaseMappingContext(), typeKey());
converter.setCustomConversions(customConversions());
return converter;
}
#Override
public String typeKey() {
return "type"; // this will owerride '_class'
}
}
Create custom MappingCouchbaseConverter
public class CustomMappingCouchbaseConverter extends MappingCouchbaseConverter {
public CustomMappingCouchbaseConverter(final MappingContext<? extends CouchbasePersistentEntity<?>,
CouchbasePersistentProperty> mappingContext, final String typeKey) {
super(mappingContext, typeKey);
this.typeMapper = new TypeBasedCouchbaseTypeMapper(typeKey);
}
}
and custom annotation #DocumentType
#Persistent
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE})
public #interface DocumentType {
String value();
}
Then create TypeAwareTypeInformationMapper which will just check if an entity is annoatated by #DocumentType if so, use value from that annotation, do the default if not (fully qualified class name)
public class TypeAwareTypeInformationMapper extends SimpleTypeInformationMapper {
#Override
public Alias createAliasFor(TypeInformation<?> type) {
DocumentType[] documentType = type.getType().getAnnotationsByType(DocumentType.class);
if (documentType.length == 1) {
return Alias.of(documentType[0].value());
}
return super.createAliasFor(type);
}
}
Then register it as following
public class TypeBasedCouchbaseTypeMapper extends DefaultTypeMapper<CouchbaseDocument> implements CouchbaseTypeMapper {
private final String typeKey;
public TypeBasedCouchbaseTypeMapper(final String typeKey) {
super(new DefaultCouchbaseTypeMapper.CouchbaseDocumentTypeAliasAccessor(typeKey),
Collections.singletonList(new TypeAwareTypeInformationMapper()));
this.typeKey = typeKey;
}
#Override
public String getTypeKey() {
return typeKey;
}
}
In your couchbase configuration class you just need to have :
#Override
public String typeKey() {
return "type";
}
Unfortunately for query derivation (n1ql) the _class or type are still using the class name.Tried spring couch 2.2.6 and it's minus point here.
#Simon, are you aware that something has changed and the support to have the possibility to have custom _class/type value in next release(s)?
#SimonBasle
Inside of class N1qlUtils and method createWhereFilterForEntity we have access to the CouchbaseConverter. On line:
String typeValue = entityInformation.getJavaType().getName();
Why not use the typeMapper from the converter to get the name of the entity when we want to avoid using the class name? Otherwise you have to annotate each method in your repository as follows:
#Query("#{#n1ql.selectEntity} WHERE `type`='airport' AND airportname = $1")
List<Airport> findAirportByAirportname(String airportName);
If createWhereFilterForEntity used the CouchbaseConverter we could avoid annotating with the #Query.

How to conditionally invoke spring validators

I have a model like below. Existing code already has validations on individual properties. Now I have a requirement to ignore existing validations on billing_country and address if buyer.method is foo. I was thinking I could have a custom validator at buyer level, check for method and invoke validations only when buyer.method!=foo. Is this a valid approach? Are there any better alternatives?
"buyer": {
"method": "foo",
"instruments": [
{
"card": {
"type": "MASTERCARD",
"number": "234234234234234",
"expire_month": "12",
"expire_year": "2017",
"billing_country" : "US"
"Address" : {
"line1": "summer st",
"city": "Boston"
"country": "US"
}
}
}
]
}
There's two ways to do this.
You can either
create a custom validation annotation and validator which checks the value of method and then applies to appropriate validations to the other fields
use validation groups, where you manually check the value of method, then choose which group to use; your annotated fields will then need to be changed to only be applied when that group is active.
Option #1
Define a class-level annotation:
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = { MethodAndInstrumentsValidator.class })
#Documented
public #interface ValidMethodAndInstruments {
String message() default "{my.package.MethodAndInstruments.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Define a validator:
public class MethodAndInstrumentsValidator
implements ConstraintValidator<ValidMethodAndInstruments, Buyer> {
public final void initialize(final ValidMethodAndInstruments annotation) {
// setup
}
public final boolean isValid(final Buyer value,
final ConstraintValidatorContext context) {
// validation logic
if(!"foo".equals(value.getMethod())) {
// do whatever conditional validation you want
}
}
}
Annotate the buyer class:
#ValidMethodAndInstruments
public class Buyer { }
Option #2
Here, you'll have to invoke the validation manually.
First, define the validation groups:
public interface FooValidation {}
public interface NotFooValidation {}
Configure a validator:
#Bean
public LocalValidatorFactoryBean validatorFactory() {
return new LocalValidatorFactoryBean();
}
In your controller(?), check the value of method and do the validation:
#Autowired
private Validator validator;
// ...
public void validate(Object a, Class<?>... groups) {
Set<ConstraintViolation<Object>> violations = validator.validate(a, groups);
if(!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
// ...
validate(buyer, "foo".equals(buyer.getMethod())
? FooValidation.class
: NotFooValidation.class);
Finally, modify the groups in your model/dto class:
public class Buyer {
// ...
#Null(groups = FooValidation.class)
#NotNull(groups = NotFooValidation.class)
protected String billingCountry;
}
#beerbajay answer helped to an extent, so I'm going to accept his answer. However I also wanted to jot down a couple of problems we ran into approach he suggested. We couldn't use #Null and #NotNull annotation on address since it could be null. Secondly since we had to wrap this in a custom Constraint validator, therefore for NotFooValidation.class we were getting extra validation message string that was not desired. Lastly we were not able to use groups in #Valid which was already present on Address. We tried #Validated but that didn't work for some reason. We ended up implementing ReaderInterceptor to intercept request and manually call validations that were needed. Hope this helps someone down the road.

Categories