Sharing data between #QueryMapping and #SchemaMapping in Java Spring GraphQL - java

I have a schema defined as follows:
enum CardType {
TYPE_1
TYPE_2
}
type Card {
id: ID!
}
type Order {
id: ID!
cards: [Card]!
}
input OrderFilter {
cardType: CardType
}
type Query {
getOrders(orderFilter: OrderFilter): [Order]
}
Also, the following resolvers:
#QueryMapping
public List<Order> getOrders(#Argument OrderFilter orderFilter) {
return this.orderService.get(orderFilter);
}
#SchemaMapping
public List<Card> cards(Order order) {
return this.cardService.getCards(order);
}
Is there a way for me to gain access to the OrderFilter argument from the #SchemaMapping annotated method? I want to filter the result from that method based on the argument of the #QueryMapping annotated method.
I tried to add an #Argument annotated parameter in the #SchemaMapping annotated method, but it does not work.

Related

Use graphql-java-extended-scalars with Quarkus

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?

How to get return type of annotated method in annotation processor?

I am learning to write custom annotations. I have a simple annotation that needs to verify if the return type of a method matches return type specified in the annotation. Below is the code.
Annotation code:
#Target(ElementType.METHOD)
public #interface ReturnCheck {
String value() default "void";
}
Annotation processor:
#SupportedAnnotationTypes("com.rajesh.customannotations.ReturnCheck")
public class ReturnCheckProcessor extends AbstractProcessor {
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for ( Element element : roundEnv.getElementsAnnotatedWith(ReturnCheck.class) ) {
//Get return type of the method
}
return false;
}
}
I want to get the return type of the annotated method so that I can compare it with the value specified in the annotation.
How can I get the return type of the method ?
Here is what you need:
if (element.getKind() == ElementKind.METHOD) {
TypeMirror returnType = ((ExecutableElement) element).getReturnType();
// use returnType for stuff ...
}
Explanation:
You can check the ElementKind in order to dispatch on its concrete type. This is the recommended way to do it instead of instanceof. After that you know its an ExecutableElement and can cast it to one.
See ExecutableElement, and Element for more details.

Mapping Hierrachical Beans using mapstruct

This is an extension to this question.
class Customer{
// distinct properties
}
class RetailCustomer extends Customer{
// distinct properties
}
class WholeSaleCustomer extends Customer{
// distinct properties
}
class CustomerDO {
// String custType ; // flag used to determine if Customer is wholeSale or Retail
//few properties same as Customer/WholeSaleCustomer/RetailCustomer
// few distinct properties
}
#Mapper
public interface CustomerMapper{
default Customer toCustomer(CustomerDO customerDO) {
String custType = customerDO.getCustType();
if("W".equalsIgnoreCase(custType)){
return toWholeSaleCustomer(customerDO);
}
else {
return toRetailCustomer(CustomerDO);
}
}
#Mappings({
#Mapping(source="a", target="b"),
#Mapping(source="c", target="d"),
#Mapping(source="m", target="m")
})
WholeSaleCustomer toWholeSaleCustomer(CustomerDO customerDO);
#Mappings({
#Mapping(source="e", target="f"),
#Mapping(source="g", target="h"),
#Mapping(source="n", target="n")
})
RetailCustomer toRetailCustomer(CustomerDO customerDO);
}
I need to map from CustomerDO to WholeSaleCustomer/RetailCustomer based on custType flag in CustomerDO. But above defined mapper doesn't work. It gives me below error while compiling
CustomerMapper.java:[23,34] Ambiguous mapping methods found for mapping property "com.domain.CustomerDO customerDO" to com.role.Customer: com.role.Customer: toCustomer
r(com.domain.CustomerDO customerDO), com.role.WholeSaleCustomer toWholeSaleCustomer(com.domain.CustomerDO wsCustomer), com.role.RetailCustomer toRetailCustomer(com.domain.CustomerDO wsCustomer)
But if I change toCustomer(CustomerDo customerDO) signature to toCustomer(Object customerDO) and remove either of toWholeSaleCustomer/toRetailCustomer, it works. It will only map either of two types. But I want both. I've similar case for Service Bean. There are serveral child Services. I should be able to map them all whenever they are required
What you are looking for is Mapping method selection based on qualifiers.
So if your customer objects look like:
class WholeSaleCustomer extends Customer {
// distinct properties
}
class CustomerDO {
// String custType ; // flag used to determine if Customer is wholeSale or Retail
//few properties same as Customer/WholeSaleCustomer/RetailCustomer
// few distinct properties
private CustomerDO customerDO;
}
Then you would have to tell MapStruct which method it needs to use to perform the mapping. So your mapper would look like:
#Mapper
public interface CustomerMapper {
#Named("baseCustomer")
default Customer toCustomer(CustomerDO customerDO) {
String custType = customerDO.getCustType();
if("W".equalsIgnoreCase(custType)){
return toWholeSaleCustomer(customerDO);
}
else {
return toRetailCustomer(CustomerDO);
}
}
#Mappings({
#Mapping(source="customerDO", qualifiedByName = "baseCustomer"),
#Mapping(source="c", target="d"),
#Mapping(source="m", target="m")
})
WholeSaleCustomer toWholeSaleCustomer(CustomerDO customerDO);
#Mappings({
#Mapping(source="customerDO", qualifiedByName = "baseCustomer"),
#Mapping(source="g", target="h"),
#Mapping(source="n", target="n")
})
RetailCustomer toRetailCustomer(CustomerDO customerDO);
}
#Named should come from org.mapstruct.Named.

Using generic types as return type for a pointcut

I'm using Spring Boot to set up a REST API. I'll be making a bunch of #RestControllers and want to set a pointcut on those methods that return a subtype of a specific abstract class I call Model. These controllers look something like this:
#RestController
public class UserController {
#RequestMapping(...)
public Person getAllPeople() {
...
}
}
Where my Person class would look something like this:
public class Person extends Model {
...
}
So would it be possible to write advice that looks something like this:
#Aspect
#Component
public class ModelAspect {
#AfterReturning(
value = "execution(<T extends mypackages.Model> T mypackages.api.*.*(..))",
returning = "model")
public void doSomethingWithModel(Model model) {
...
}
}
Of course that won't work because the advice is not valid syntactically. In the reference documentation, I have only found information about generic parameters, not return types (Spring AOP reference). What I have now is this, but I think something like the example above would be a lot more efficient:
#Aspect
#Component
public class ModelAspect {
#AfterReturning(
value = "execution(* mypackages.api.*.*(..))",
returning = "model")
public void doSomething(Object model) {
if (model instanceof Model)
doSomethingWithModel((Model) model);
}
}
My next question would be, is the same possible for those methods that return a Collection of suptypes of Model? Because the reference states that parameter types cannot be generic Collections.
Have you tried using + after your interface?
#Aspect
#Component
public class ModelAspect {
#AfterReturning(
value = "execution(mypackages.Model+ mypackages.api.*.*(..))",
returning = "model")
public void doSomethingWithModel(Model model) {
...
}
}
You could try do not specify the return type. Based on the documentation it will be resolved by the type of the parameter used at the returning clause:
A returning clause also restricts matching to only those method
executions that return a value of the specified type ( Object in this
case, which will match any return value).
#Aspect
#Component
public class ModelAspect {
#AfterReturning(
value = "execution(* mypackages.api.*.*(..))",
returning = "model")
public void doSomethingWithModel(Model model) {
...
}
}
Have a look to the below link. It answers also your second question, about generic collections.
Aspectj Matching Return Type
Just for curiosity I have created a project for testing this and it started working for me straight forward. I can only think the path your pointcut is pointing to is wrong. Try with:
#Aspect
#Component
public class ModelAspect {
#AfterReturning(
value = "execution(* mypackages.api..*(..))",
returning = "model")
public void doSomethingWithModel(Model model) {
...
}
}
You can have a look to my project at: spring-aspectj-interfaces
There you will see different values for the pointcut (only one not commented, of course), all of them valid.

Conditional injection of bean

I want to have inject a bean based on a String parameter passed from client.
public interface Report {
generateFile();
}
public class ExcelReport extends Report {
//implementation for generateFile
}
public class CSVReport extends Report {
//implementation for generateFile
}
class MyController{
Report report;
public HttpResponse getReport() {
}
}
I want report instance to be injected based on the parameter passed. Any help would be greatly appretiated. Thanks in advance
Use Factory method pattern:
public enum ReportType {EXCEL, CSV};
#Service
public class ReportFactory {
#Resource
private ExcelReport excelReport;
#Resource
private CSVReport csvReport
public Report forType(ReportType type) {
switch(type) {
case EXCEL: return excelReport;
case CSV: return csvReport;
default:
throw new IllegalArgumentException(type);
}
}
}
The report type enum can be created by Spring when you call your controller with ?type=CSV:
class MyController{
#Resource
private ReportFactory reportFactory;
public HttpResponse getReport(#RequestParam("type") ReportType type){
reportFactory.forType(type);
}
}
However ReportFactory is pretty clumsy and requires modification every time you add new report type. If the report types list if fixed it is fine. But if you plan to add more and more types, this is a more robust implementation:
public interface Report {
void generateFile();
boolean supports(ReportType type);
}
public class ExcelReport extends Report {
publiv boolean support(ReportType type) {
return type == ReportType.EXCEL;
}
//...
}
#Service
public class ReportFactory {
#Resource
private List<Report> reports;
public Report forType(ReportType type) {
for(Report report: reports) {
if(report.supports(type)) {
return report;
}
}
throw new IllegalArgumentException("Unsupported type: " + type);
}
}
With this implementation adding new report type is as simple as adding new bean implementing Report and a new ReportType enum value. You could get away without the enum and using strings (maybe even bean names), however I found strongly typing beneficial.
Last thought: Report name is a bit unfortunate. Report class represents (stateless?) encapsulation of some logic (Strategy pattern), whereas the name suggests it encapsulates value (data). I would suggest ReportGenerator or such.

Categories