use of io.github.microcks.domain.Header in project microcks by microcks.
the class GraphQLController method execute.
@RequestMapping(value = "/{service}/{version}/**", method = { RequestMethod.GET, RequestMethod.POST })
public ResponseEntity<?> execute(@PathVariable("service") String serviceName, @PathVariable("version") String version, @RequestParam(value = "delay", required = false) Long delay, @RequestBody(required = false) String body, HttpServletRequest request) {
log.info("Servicing GraphQL mock response for service [{}, {}]", serviceName, version);
log.debug("Request body: {}", body);
long startTime = System.currentTimeMillis();
// If serviceName was encoded with '+' instead of '%20', remove them.
if (serviceName.contains("+")) {
serviceName = serviceName.replace('+', ' ');
}
Service service = serviceRepository.findByNameAndVersion(serviceName, version);
GraphQLHttpRequest graphqlHttpReq;
Document graphqlRequest;
try {
graphqlHttpReq = GraphQLHttpRequest.from(body, request);
graphqlRequest = requestParser.parseDocument(graphqlHttpReq.getQuery());
} catch (Exception e) {
log.error("Error parsing GraphQL request: {}", e);
return new ResponseEntity<Object>("Error parsing GraphQL request: " + e.getMessage(), HttpStatus.BAD_REQUEST);
}
Definition graphqlDef = graphqlRequest.getDefinitions().get(0);
OperationDefinition graphqlOperation = (OperationDefinition) graphqlDef;
log.debug("Got this graphqlOperation: {}", graphqlOperation);
// Operation type is direct but name depends on syntax...
String operationName = graphqlHttpReq.getOperationName();
String operationType = graphqlOperation.getOperation().toString();
// Check is it's an introspection query to handle first.
if ("QUERY".equals(operationType) && INTROSPECTION_SELECTION.equals(((Field) graphqlOperation.getSelectionSet().getSelections().get(0)).getName())) {
log.info("Handling GraphQL schema introspection query...");
Resource graphqlSchema = resourceRepository.findByServiceIdAndType(service.getId(), ResourceType.GRAPHQL_SCHEMA).get(0);
TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(graphqlSchema.getContent());
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, RuntimeWiring.MOCKED_WIRING);
GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();
ExecutionResult executionResult = graphQL.execute(graphqlHttpReq.getQuery());
String responseContent = null;
try {
responseContent = mapper.writeValueAsString(executionResult);
} catch (JsonProcessingException jpe) {
log.error("Unknown Json processing exception", jpe);
return new ResponseEntity<>(jpe.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<Object>(responseContent, HttpStatus.OK);
}
// Then deal with one or many regular GraphQL selection queries.
List<GraphQLQueryResponse> graphqlResponses = new ArrayList<>();
final Long[] maxDelay = { (delay == null ? 0L : delay) };
for (Selection selection : graphqlOperation.getSelectionSet().getSelections()) {
GraphQLQueryResponse graphqlResponse = null;
try {
graphqlResponse = processGraphQLQuery(service, operationType, (Field) selection, graphqlRequest.getDefinitionsOfType(FragmentDefinition.class), body, graphqlHttpReq, request);
graphqlResponses.add(graphqlResponse);
if (graphqlResponse.getOperationDelay() != null && graphqlResponse.getOperationDelay() > maxDelay[0]) {
maxDelay[0] = graphqlResponse.getOperationDelay();
}
} catch (GraphQLQueryProcessingException e) {
log.error("Caught a GraphQL processing exception", e);
return new ResponseEntity<Object>(e.getMessage(), e.getStatus());
}
}
/* Optimized parallel version but need to better handle exception.
graphqlResponses = graphqlOperation.getSelectionSet().getSelections().stream().parallel().map(selection -> {
try {
GraphQLQueryResponse graphqlResponse = processGraphQLQuery(service, operationType, (Field) selection,
graphqlRequest.getDefinitionsOfType(FragmentDefinition.class), body, graphqlHttpReq, request);
if (graphqlResponse.getOperationDelay() != null && graphqlResponse.getOperationDelay() > maxDelay[0]) {
maxDelay[0] = graphqlResponse.getOperationDelay();
}
return graphqlResponse;
} catch (GraphQLQueryProcessingException e) {
log.error("Caught a GraphQL processing exception", e);
return null;
}
}).collect(Collectors.toList());
*/
// Deal with response headers.
HttpHeaders responseHeaders = new HttpHeaders();
for (GraphQLQueryResponse response : graphqlResponses) {
if (response.getResponse().getHeaders() != null) {
for (Header header : response.getResponse().getHeaders()) {
if (!HttpHeaders.TRANSFER_ENCODING.equalsIgnoreCase(header.getName())) {
responseHeaders.put(header.getName(), new ArrayList<>(header.getValues()));
}
}
}
}
if (!responseHeaders.containsKey("Content-Type") && !responseHeaders.containsKey("content-type")) {
responseHeaders.put("Content-Type", List.of("application/json"));
}
// Waiting for delay if any.
MockControllerCommons.waitForDelay(startTime, maxDelay[0]);
// Publish an invocation event before returning if enabled.
if (enableInvocationStats) {
MockControllerCommons.publishMockInvocation(applicationContext, this, service, graphqlResponses.get(0).getResponse(), startTime);
}
String responseContent = null;
JsonNode responseNode = graphqlResponses.get(0).getJsonResponse();
// If multi-queries and aliases were used, recompose an aggregated result.
if (graphqlResponses.size() > 1) {
ObjectNode aggregated = mapper.createObjectNode();
ObjectNode dataNode = aggregated.putObject("data");
for (GraphQLQueryResponse response : graphqlResponses) {
dataNode.set(response.getAlias(), response.getJsonResponse().path("data").path(response.getOperationName()).deepCopy());
}
responseNode = aggregated;
}
try {
responseContent = mapper.writeValueAsString(responseNode);
} catch (JsonProcessingException e) {
log.error("Unknown Json processing exception", e);
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<Object>(responseContent, responseHeaders, HttpStatus.OK);
}
use of io.github.microcks.domain.Header in project microcks by microcks.
the class AsyncAPIImporter method extractFromAsyncAPI21Example.
/**
* Extract example using the AsyncAPI 2.1 new 'name' property.
*/
private EventMessage extractFromAsyncAPI21Example(String contentType, JsonNode exampleNode) {
// Retrieve name & payload value.
String exampleName = exampleNode.path("name").asText();
String exampleValue = getExamplePayload(exampleNode);
// Build and store a request object.
EventMessage eventMessage = new EventMessage();
eventMessage.setName(exampleName);
eventMessage.setContent(exampleValue);
eventMessage.setMediaType(contentType);
// Now complete with specified headers.
List<Header> headers = getExampleHeaders(exampleNode);
for (Header header : headers) {
eventMessage.addHeader(header);
}
return eventMessage;
}
use of io.github.microcks.domain.Header in project microcks by microcks.
the class AMQPProducerManager method renderEventMessageHeaders.
/**
* Render Microcks headers using the template engine.
* @param engine The template engine to reuse (because we do not want to initialize and manage a context at the KafkaProducerManager level.)
* @param headers The Microcks event message headers definition.
* @return A set of rendered Microcks headers.
*/
public Set<Header> renderEventMessageHeaders(TemplateEngine engine, Set<Header> headers) {
if (headers != null && !headers.isEmpty()) {
Set<Header> renderedHeaders = new HashSet<>(headers.size());
for (Header header : headers) {
String firstValue = header.getValues().stream().findFirst().get();
if (firstValue.contains(TemplateEngine.DEFAULT_EXPRESSION_PREFIX)) {
try {
Header renderedHeader = new Header();
renderedHeader.setName(header.getName());
renderedHeader.setValues(Set.of(engine.getValue(firstValue)));
renderedHeaders.add(renderedHeader);
} catch (Throwable t) {
logger.error("Failing at evaluating template " + firstValue, t);
Header renderedHeader = new Header();
renderedHeader.setName(header.getName());
renderedHeader.setValues(Set.of(firstValue));
renderedHeaders.add(renderedHeader);
}
} else {
Header renderedHeader = new Header();
renderedHeader.setName(header.getName());
renderedHeader.setValues(Set.of(firstValue));
renderedHeaders.add(renderedHeader);
}
}
}
return null;
}
use of io.github.microcks.domain.Header in project microcks by microcks.
the class AMQPProducerManager method publishMessage.
/**
* Publish a message on specified destination.
* @param destinationType The type of destination (queue, topic, fanout, ...)
* @param destinationName The name of destination
* @param value The message payload
* @param headers A set of headers if any (maybe null or empty)
*/
public void publishMessage(String destinationType, String destinationName, String value, Set<Header> headers) {
logger.infof("Publishing on destination {%s}, message: %s ", destinationName, value);
try {
Channel channel = amqpConnection.createChannel();
channel.exchangeDeclare(destinationName, destinationType);
AMQP.BasicProperties properties = null;
// Adding headers to properties if provided.
if (headers != null && headers.size() > 0) {
Map<String, Object> amqpHeaders = new HashMap<>();
for (Header header : headers) {
amqpHeaders.put(header.getName(), header.getValues().toArray()[0]);
}
properties = new AMQP.BasicProperties.Builder().headers(amqpHeaders).build();
}
channel.basicPublish(destinationName, "", properties, value.getBytes(StandardCharsets.UTF_8));
channel.close();
} catch (IOException | TimeoutException ioe) {
logger.warnf("Message %s sending has thrown an exception", ioe);
ioe.printStackTrace();
}
}
use of io.github.microcks.domain.Header in project microcks by microcks.
the class GraphQLController method processGraphQLQuery.
/**
* Process a GraphQL field selection query (an Http query may contain many field selection queries).
* @param service The Service this query is targeting
* @param operationType The type of GraphQL operation (QUERY or MUTATION)
* @param graphqlField The Field selection we should apply
* @param fragmentDefinitions A list of fragment field selection
* @param body The Http request body
* @param graphqlHttpReq The Http GraphQL request wrapper
* @param request The bare Http Servlet request
* @return A GraphQL query response wrapper with some elements from the Microcks domain matching Response
* @throws GraphQLQueryProcessingException if incoming field selection query cannot be processed
*/
protected GraphQLQueryResponse processGraphQLQuery(Service service, String operationType, Field graphqlField, List<FragmentDefinition> fragmentDefinitions, String body, GraphQLHttpRequest graphqlHttpReq, HttpServletRequest request) throws GraphQLQueryProcessingException {
GraphQLQueryResponse result = new GraphQLQueryResponse();
String operationName = graphqlField.getName();
result.setAlias(graphqlField.getAlias());
result.setOperationName(operationName);
log.debug("Processing a '{}' operation with name '{}'", operationType, operationName);
Operation rOperation = null;
for (Operation operation : service.getOperations()) {
// Select operation based on type (QUERY or MUTATION)...
if (operation.getMethod().equals(operationType)) {
// ... then chek the operation name itself.
if (operation.getName().startsWith(operationName)) {
rOperation = operation;
break;
}
}
}
if (rOperation != null) {
log.debug("Found a valid operation {} with rules: {}", rOperation.getName(), rOperation.getDispatcherRules());
String violationMsg = validateParameterConstraintsIfAny(rOperation, request);
if (violationMsg != null) {
throw new GraphQLQueryProcessingException(violationMsg + ". Check parameter constraints.", HttpStatus.BAD_REQUEST);
}
// We must find dispatcher and its rules. Default to operation ones but
// if we have a Fallback this is the one who is holding the first pass rules.
String dispatcher = rOperation.getDispatcher();
String dispatcherRules = rOperation.getDispatcherRules();
FallbackSpecification fallback = MockControllerCommons.getFallbackIfAny(rOperation);
if (fallback != null) {
dispatcher = fallback.getDispatcher();
dispatcherRules = fallback.getDispatcherRules();
}
//
String dispatchCriteria = computeDispatchCriteria(dispatcher, dispatcherRules, graphqlField, graphqlHttpReq.getVariables(), request, body);
log.debug("Dispatch criteria for finding response is {}", dispatchCriteria);
// First try: using computed dispatchCriteria on main dispatcher.
Response response = null;
List<Response> responses = responseRepository.findByOperationIdAndDispatchCriteria(IdBuilder.buildOperationId(service, rOperation), dispatchCriteria);
if (!responses.isEmpty()) {
response = responses.get(0);
}
if (response == null) {
// When using the SCRIPT dispatcher, return of evaluation may be the name of response.
log.debug("No responses with dispatch criteria, trying the name...");
responses = responseRepository.findByOperationIdAndName(IdBuilder.buildOperationId(service, rOperation), dispatchCriteria);
if (!responses.isEmpty()) {
response = responses.get(0);
}
}
if (response == null && fallback != null) {
// If we've found nothing and got a fallback, that's the moment!
log.debug("No responses till now so far, applying the fallback...");
responses = responseRepository.findByOperationIdAndName(IdBuilder.buildOperationId(service, rOperation), fallback.getFallback());
if (!responses.isEmpty()) {
response = responses.get(0);
}
}
if (response == null) {
// In case no response found (because dispatcher is null for example), just get one for the operation.
// This will allow also OPTIONS operations (like pre-flight requests) with no dispatch criteria to work.
log.debug("No responses found so far, tempting with just bare operationId...");
responses = responseRepository.findByOperationId(IdBuilder.buildOperationId(service, rOperation));
if (!responses.isEmpty()) {
response = responses.get(0);
}
}
if (response != null) {
result.setResponse(response);
result.setOperationDelay(rOperation.getDefaultDelay());
// Prepare headers for evaluation.
Map<String, String> evaluableHeaders = new HashMap<>();
if (response.getHeaders() != null) {
for (Header header : response.getHeaders()) {
evaluableHeaders.put(header.getName(), request.getHeader(header.getName()));
}
}
// Render response content before waiting and returning.
String responseContent = MockControllerCommons.renderResponseContent(body, null, evaluableHeaders, response);
try {
JsonNode responseJson = mapper.readTree(responseContent);
filterFieldSelection(graphqlField.getSelectionSet(), fragmentDefinitions, responseJson.get("data").get(operationName));
result.setJsonResponse(responseJson);
} catch (JsonProcessingException pe) {
log.error("JsonProcessingException while filtering response according GraphQL field selection", pe);
throw new GraphQLQueryProcessingException("Exception while filtering response JSON", HttpStatus.INTERNAL_SERVER_ERROR);
}
return result;
}
log.debug("No response found. Throwing a BAD_REQUEST exception...");
throw new GraphQLQueryProcessingException("No matching response found", HttpStatus.BAD_REQUEST);
}
log.debug("No valid operation found. Throwing a NOT_FOUND exception...");
throw new GraphQLQueryProcessingException("No '" + operationName + "' operation found", HttpStatus.NOT_FOUND);
}
Aggregations