Graphql return enum collection - java

I want to return all values of enum using graphql.
I have schema:
schema {
query: Query
}
type Query {
getDataTypes: [DictionaryType]
}
enum DictionaryType{
RISK
SALES_CHANNEL
PERSON_TYPE
}
We have normal java enum:
public enum DictionaryType {
RISK,
SALES_CHANNEL,
PERSON_TYPE
}
and Controller with configuration:
public class DictionaryController {
#Value("classpath:items.graphqls")
private Resource schemaResource;
private GraphQL graphQL;
private final DictionaryService dictionaryService;
#PostConstruct
public void loadSchema() throws IOException {
File schemaFile = schemaResource.getFile();
TypeDefinitionRegistry registry = new SchemaParser().parse(schemaFile);
RuntimeWiring wiring = buildWiring();
GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(registry, wiring);
graphQL = GraphQL.newGraphQL(schema).build();
}
private RuntimeWiring buildWiring() {
DataFetcher<Set<DictionaryType>> fetcher3 = dataFetchingEnvironment -> {
return dictionaryService.getDictionaryTypes();
};
return RuntimeWiring.newRuntimeWiring().type("Query", typeWriting ->
typeWriting
.dataFetcher("getDataTypes", fetcher3))
.build();
}
#PostMapping("getDataTypes")
public ResponseEntity<Object> getDataTypes(#RequestBody String query) {
ExecutionResult result = graphQL.execute(query);
return new ResponseEntity<Object>(result, HttpStatus.OK);
}
}
When i make POST to http://localhost:50238/getDataTypes
with body:
{
getDataTypes {
}
}
I get "errorType": "InvalidSyntax",
in response.

That's an invalid query as you have braces with no content (i.e. { }). Your schema suggests that the query should be much simpler though:
{ getDataTypes }

Related

#JsonView on Spring #GetMapping query parameter objects

In Spring, is it possible to use #JsonView on URL query parameter objects? I can do it for #RequestBody but we don't have bodies in GET requests. This question is specifically for URL query parameters that have been converted to objects by Spring.
For example, I want to have a controller with this mapping:
#GetMapping("/user")
ResponseEntity<UserDTO> searchUser(#JsonView(value = UserView.Searchable.class) UserDTO userQuery) {
//Do some work here using userQuery object for searching users
return ResponseEntity.ok();
}
UserDTO:
public class UserDTO {
#JsonProperty("id")
#JsonView(UserView.Private.class)
private String id= null;
#JsonView(UserView.Searchable.class)
#JsonProperty("city")
private String city = null;
#JsonProperty("country")
#JsonView(UserView.Searchable.class)
private String country = null;
#JsonProperty("state")
private String state = null;
#JsonProperty("zipCode")
private String zipCode = null;
//More properties and getter/setters...etc
}
So if I wanted to call the endpoint I could create a URL like
localhost:8080//api/user?country=Canada to search for a user in Canada but if I tried localhost:8080//api/user?id=123, the property would be ignored.
EDIT:
I might have rushed this idea. There is no JSON de-serialization from url parameters because they are not JSON. Spring creates the query object from ServletModelAttributeMethodProcessor. Perhaps if I want some custom behavior I need to implement HandlerMethodArgumentResolver and do it myself.
EDIT 2
I'm a bit new to Spring so I have a lot to learn but I think what Ill do is just use #InitBinder to whitelist the fields for binding
#InitBinder
public void setSearchableFields(WebDataBinder binder) {
binder.setAllowedFields(
"city",
"country"
);
}
In one of my projects, I have used a POJO just for the query params. I doubt any property would be ignored by default in spring, you can have null checks to ignore.
QueryParams.java
#Data
public class QueryParams {
Integer page;
Integer pageSize;
String sortBy;
Sort.Direction direction;
String searchId;
String status;
String symbol;
public PageRequest getPageRequest(){
if(this.page==null){
this.page = 0;
}
if(this.pageSize==null){
this.pageSize = 25;
}
if(this.sortBy==null){
this.sortBy = "createdAt";
}
if(this.direction ==null){
this.direction = Sort.Direction.DESC;
}
return PageRequest.of(this.page, this.pageSize, Sort.by(this.direction, this.sortBy));
}
}
And my controller :
#GetMapping("currencies")
public ResponseEntity<Page<CurrencyConfig>> getAllCurrencies(#Valid QueryParams queryParams) {
try {
return ResponseEntity.ok(orderbookService.getAllCurrencyConfig(queryParams));
} catch (HttpClientErrorException | HttpServerErrorException e) {
throw new ResponseStatusException(e.getStatusCode(), e.getMessage());
} catch (Exception e) {
e.printStackTrace();
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,
e.getLocalizedMessage());
}
}

How to manipulate data form microservice response in Java, Spring Boot [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
I am working on a project in Spring Boot that has a microservice architecture. I needed to make a call from one microservice to another and with data that I get as response do something. I am very new to all microservice architecture and Spring Boot, so I figured I need a small push :)
So I have this class:
HttpDataClient.java
public class HttpDataClient implements DataClient{
private final static Logger LOGGER = LoggerFactory.getLogger(HttpDataClient.class);
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper = new ObjectMapper();
public HttpDataClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
#Override
public DataResponse getData(String dataId) {
try{
JsonNode node = restTemplate.exchange(
String.format("/data/%s", dataId),
HttpMethod.POST,
new HttpEntity<>(buildRequest(dataId), headers()),
JsonNode.class
).getBody();
return dataResponse(node);
}catch (HttpStatusCodeException e) {
String msg = String.format(
"Error getting data for dataId: %s",
dataId,
e.getStatusCode(),
e.getResponseBodyAsString());
LOGGER.error(msg);
return dataResponse.failed();
}
}
private MultiValueMap<String, String> headers() {
final LinkedMultiValueMap<String, String> mv = new LinkedMultiValueMap<>();
mv.set(HttpHeaders.CONTENT_TYPE, "application/json");
return mv;
}
private DataResponse dataResponse(JsonNode node) {
return DataResponse.dataResponse(
asString(node, "dataId"),
asString(node, "author"),
asString(node, "authorDataId"),
asString(node, "serverSideDataId")
);
}
private JsonNode buildRequest(String dataId) {
ObjectNode root = objectMapper.createObjectNode();
root.put("dataId", dataId);
return root;
}
}
And the interface
public interface DataClient {
DataResponse getData(String dataId);
}
And from this class when I get response I should be able to do next if author is "Philadelphia" then athorDataId and serverSideId are the same and if author is not "Philadelphia" then athorDataId and serverSideId are not the same.
For now, I have created this class:
public class DataResolver {
private final HttpDataClient client;
public DataResolver(HttpDataClient client) {
this.client = client;
}
}
And in this class, I should execute all of this:
And from this class when I get response I should be able to do next if author is "Philadelphia" then athorDataId and serverSideId are the same and if author is not "Philadelphia" then athorDataId and serverSideId are not the same.
But I don't know how to start. I know I suppose to use client to get the response data. I am not sure how to use it.. And then I should probably do something like this :
if (author == Philadelphia) {
authorDataId == serverSideDataId
} elseif(author != Philadelphia) {
authorDataId != serverSideDataId
}
UPDATE
Is it possible to create new class like this
public class DataResolver {
private final HttpDataClient client;
public DataResolver(HttpDataClient client) {
this.client = client;
}
}
And in this class access data from response and manipulate it in some way?
Something like this:
public class DataResolver {
private final HttpDataClient client;
public DataResolver(HttpDataClient client) {
this.client = client;
}
public DataIdResolver idResolver() {
if (author == Philadelphia) {
authorDataId == serverSideDataId
} elseif(author != Philadelphia) {
authorDataId != serverSideDataId
}
}
}
If the response that you expect should contain the mentioned attributes, then you should create the following class and use it as the return type in restTemplate.exchange instead of JsonNode.class (assuming you have Jackson in your classpath):
public class DataClient {
private String dataId;
private String author;
private String authorDataId;
private String serverSideDataId;
// getters and setters
}
So you would have something like in the HttpDataClient class:
#Override
public DataClient getData(String dataId) {
try{
DataClient data = restTemplate.exchange(
String.format("/data/%s", dataId),
HttpMethod.POST,
new HttpEntity<>(buildRequest(dataId), headers()),
DataClient.class).getBody();
return data;
}catch (HttpStatusCodeException e) {
String msg = String.format(
"Error getting data for dataId: %s",
dataId,
e.getStatusCode(),
e.getResponseBodyAsString());
LOGGER.error(msg);
return dataResponse.failed();
}
}
And DataResolver:
#Component
public class DataResolver {
private final HttpDataClient client;
public DataResolver(HttpDataClient client) {
this.client = client;
}
public DataClient idResolver(String dataId) {
DataClient data = client.getData(dataId);
// Whatever logic you need
return data;
}
}
Maybe one important thing here is that you most likely would want to make HttpDataClient a Spring bean by adding the #Service annotation. By doing this you can autowire it to any other Spring bean you need.

Controller in DGS Netflix Graphql

We are developing a project Using Spring boot with DGS Netflix graphql. We are created all the schemas and datafethers which is working absolutely fine with a default endpoint "/graphql". we would like to expose this app with custom endpoing so we are trying to add a controller with a custom endpoint as below. But When i run the application and send a query, my data fetcher is called twice . first time called from controller and second time i believe from framework it self. Anybody has any thoughts on this why its being called twice and how to avoid it? You help is highly appreciated. Please see the below Datafetcher and Controller.
Controller:
#RestController
#RequestMapping("/sample-information/model")
#Slf4j
public class CustomController {
#Autowired
DgsQueryExecutor dgsQueryExecutor;
#PostMapping(consumes = {MediaType.APPLICATION_JSON_VALUE, "application/graphql"})
public Mono<ResponseEntity<Object>> getDetails(#RequestBody String query,
#RequestHeader HttpHeaders headers
) {
GraphQLQueryInput inputs = null;
try {
inputs = ObjectMapperHelper.objectMapper.readValue(query, GraphQLQueryInput.class);
} catch (Exception e) {
log.error("TraceId: {} - Application Error: Error message: Error converting query to GraphQLQueryInput: {} "+ query);
}
if(inputs.getVariables() == null) {
inputs.setVariables(new HashMap<>());
}
if(inputs.getOperationName() == null) {
inputs.setOperationName("");
}
final String que = inputs.getQuery();
final Map<String, Object> var = inputs.getVariables();
final String opn = inputs.getOperationName();
ExecutionInput.Builder executionInput = ExecutionInput.newExecutionInput()
.query(inputs.getQuery())
.operationName(inputs.getOperationName())
.variables(inputs.getVariables());
return Mono.fromCallable(()-> {
return dgsQueryExecutor.execute(que, var, opn);
}).subscribeOn(Schedulers.elastic()).map(result -> {
return new ResponseEntity<>(result, HttpStatus.OK);
});
}
}
Datafetcher:
#DgsComponent
#Slf4j
public class SampleDataFetcher {
#Autowired
SampleBuilder sampleBuilder;
#DgsData(parentType = DgsConstants.QUERY_TYPE, field = DgsConstants.QUERY.SampleField)
public CompletableFuture<StoreDirectoryByStateResponse> getStoreDirectoryByState(#InputArgument String state, DataFetchingEnvironment dfe) throws BadRequestException {
Mono<StoreDirectoryByStateResponse> storeDirectoryResponse = null;
try {
sampleResponse = sampleBuilder.buildResponse(modelGraphQLContext);
} catch (BaseException e) {
}
return sampleResponse.map(response -> {
return response;
}).toFuture();
}
}

Springboot: How to make Rest Service Controller non-blocking

I have a spring boot application which validates a file for a given client id and returns a json response of validation errors and warnings, while performing load test we noticed most the requests being blocked, so am trying to make our application non blocking by leveraging Spring's non blocking api
Below is my spring version
springBootVersion = '1.5.3.RELEASE'
springVersion = '4.3.8.RELEASE'
Below is my springboot ValidationController.groovy which is blocking requests
#Controller
#ResponseBody
class ValidationController {
#RequestMapping(value = "/validate", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseEntity<ValidationResult> validate(#RequestParam(value = "file", required = true) MultipartFile file,
#RequestParam(value = "client_id", required = true) String clientId)
{
if (clientId.isEmpty()) {
String msg = "client id is required"
if (LOGGER.isErrorEnabled()) {
LOGGER.error(msg)
}
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
}
String contentType = file.contentType.toLowerCase();
if (LOGGER.isDebugEnabled()) LOGGER.debug("content type = $contentType");
Client client = clientRepository.findByExternalId(clientId)
if (client == null) {
String msg = "client id is invalid"
if (LOGGER.isErrorEnabled()) {
LOGGER.error(msg)
}
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
}
if (file.isEmpty()) {
String msg = "file is empty"
if(LOGGER.isErrorEnabled()) {
LOGGER.error(msg)
}
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
}
ValidationResult result = validationService.validate(file, client);
return ResponseEntity.ok(result)
}
}
class ValidationResult {
private List<Warning> warnings
private List<Error> errors
//getters setters for warnings and errors
}
class Warning {
private String message
private String type
//getters setters for message and type
}
class Error {
private String message
private String type
//getters setters for message and type
}
I have modified my ValidationController.groovy as below
#Controller
#ResponseBody
class ValidationController {
#Autowired
#Qualifier("postRequestExecutorService")
private ExecutorService postRequestExecutor;
#RequestMapping(value = "/validate", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public DeferredResult<ResponseEntity<ValidationResult>> validate(#RequestParam(value = "file", required = true) MultipartFile file,
#RequestParam(value = "client_id", required = true) String clientId)
{
DeferredResult<ResponseEntity<ValidationResult>> deferredResult = new DeferredResult<>();
CompletableFuture.supplyAsync(() -> validate(clientId, file), postRequestExecutor)
.whenComplete((result, throwable) ->
{
deferredResult.setResult(result);
} );
}
private ResponseEntity<ValidationResult> validateLedes(String clientId, MultipartFile file) {
ValidationResult result;
try{
if (clientId.isEmpty()) {
String msg = messageSource.getMessage("client.id.required", null, Locale.getDefault())
LOGGER.error(msg)
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
}
String contentType = file.contentType.toLowerCase();
if (LOGGER.isDebugEnabled()) LOGGER.debug("content type = $contentType");
Client client = clientRepository.findByExternalId(clientId)
if (client == null) {
String msg = messageSource.getMessage("client.id.invalid", null, Locale.getDefault())
LOGGER.error(msg)
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
}
if (file.isEmpty()) {
String msg = messageSource.getMessage("ledes.file.empty", null, Locale.getDefault())
LOGGER.error(msg)
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
}
result = validationService.validate(file, Ledesxmlebilling21.class, client);
}
catch (Exception ex){
LOGGER.error("Exception in validateLedes = "+ex.message)
LOGGER.error("StackTrace in validateLedes = "+ex.stackTrace)
}
return ResponseEntity.ok(result)
}
}
And below is my ExecutorServiceConfiguration
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
#Configuration
public class RequestsExecuterServiceConfiguration {
/**
* Dedicated Thread Modeling to handle POST Requests
*/
#Bean
public ExecutorService postRequestExecutorService() {
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("postRequestExecutor-%d")
.setDaemon(true)
.build();
ExecutorService es = Executors.newFixedThreadPool(10,threadFactory);
return es;
}
}
Since my controller is a groovy class am seeing some compiler errors for the CompletableFuture lambda expression, can someone please help me make it work for groovy controller?
UPDATE1
As per the Answer I've changed the labda expression to closure as below
#RequestMapping(value = "/validate", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public DeferredResult<ResponseEntity<ValidationResult>> validate(#RequestParam(value = "file", required = true) MultipartFile file,
#RequestParam(value = "client_id", required = true) String clientId)
{
DeferredResult<ResponseEntity<ValidationResult>> deferredResult = new DeferredResult<ResponseEntity<ValidationResult>>();
CompletableFuture.supplyAsync({ -> validateLedes(clientId, file) }, postRequestExecutor)
.whenComplete({ futureResult, throwable -> deferredResult.setResult(futureResult);})
deferredResult
}
With the above controller, am not getting below errors
2018-04-11 15:07:45 - Exception in validateLedes = failed to lazily initialize a collection of role: com.validation.entity.Client.ruleConfigurations, could not initialize proxy - no Session
2018-04-11 15:07:45 - StackTrace in validateLedes = org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:587), org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:204), org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:148), org.hibernate.collection.internal.PersistentSet.size(PersistentSet.java:143)
Looks like the issue is, Hibernate session is not bound to ExecutorService and the new thread in which validateLedes method is executing it's unable to read from the database, can someone please me to binding Hibernate session to the the ExecutorService's thread pool?
You can't just stick lambdas into Groovy (until Groovy 3)
You'll need to translate them to Closures, so for example:
() -> validate(clientId, file)
becomes:
{ -> validate(clientId, file) }
And
(result, throwable) ->
{
deferredResult.setResult(result);
}
would be:
{ result, throwable -> deferredResult.setResult(result) }

Json dynamic deserialization with jackson

I've already have a look at the question "Jackson dynamic property names" but it does not really answer to my question.
I want to deserialize something like this :
public class Response<T> {
private String status;
private Error error;
private T data;
}
but data can have different names since different services exist and return the same structure with some different data. For example 'user' and 'contract' :
{
response: {
status: "success",
user: {
...
}
}
}
or
{
response: {
status: "failure",
error : {
code : 212,
message : "Unable to retrieve contract"
}
contract: {
...
}
}
}
I'd like genericize my responses objects like this :
public class UserResponse extends Response<User> {}
I've tried the following but i'm not sure it is my use case or if don't use it in the good way :
#JsonTypeInfo(include = As.WRAPPER_OBJECT, use = Id.CLASS)
#JsonSubTypes({#Type(value = User.class, name = "user"),
#Type(value = Contract.class, name = "contract")})
Finally, i've created a custom Deserializer. It works but i'm not satisfied:
public class ResponseDeserializer extends JsonDeserializer<Response> {
#Override
public Response deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Response responseData = new Response();
Object data = null;
for (; jp.getCurrentToken() != JsonToken.END_OBJECT; jp.nextToken()) {
String propName = jp.getCurrentName();
// Skip field name:
jp.nextToken();
if ("contract".equals(propName)) {
data = mapper.readValue(jp, Contract.class);
} else if ("user".equals(propName)) {
data = mapper.readValue(jp, User.class);
} else if ("status".equals(propName)) {
responseData.setStatus(jp.getText());
} else if ("error".equals(propName)) {
responseData.setError(mapper.readValue(jp, com.ingdirect.dg.business.object.community.api.common.Error.class));
}
}
if (data instanceof Contract) {
Response<Contract> response = new Response<Ranking>(responseData);
return response;
}
if (data instanceof User) {
Response<User> response = new Response<User>(responseData);
return response;
}
// in all other cases, the type is not yet managed, add it when needed
throw new JsonParseException("Cannot parse this Response", jp.getCurrentLocation());
}
}
Any idea to do this clean with annotations ? Thanks in advance !
Jackson framework provides inbuilt support for dynamic types.
//Base type
#JsonTypeInfo(property = "type", use = Id.NAME)
#JsonSubTypes({ #Type(ValidResponse.class),
#Type(InvalidResponse.class)
})
public abstract class Response<T> {
}
//Concrete type 1
public class ValidResponse extends Response<T>{
}
//Concrete type 2
public class InvalidResponse extends Response<T>{
}
main {
ObjectMapper mapper = new ObjectMapper();
//Now serialize
ValidResponse response = (ValidResponse)(mapper.readValue(jsonString, Response.class));
//Deserialize
String jsonString = mapper.writeValueAsString(response);
}
Have you tried:
public class AnyResponse {
private String status;
private Error error;
private Contract contract;
private User user;
// And all other possibilities.
}
// ...
mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
This should fill in whatever object appears in the JSON and leave the rest null.
You could then fill in a Response with the relevant object.

Categories