Search in sources :

Example 11 with Header

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);
}
Also used : HttpHeaders(org.springframework.http.HttpHeaders) TypeDefinitionRegistry(graphql.schema.idl.TypeDefinitionRegistry) ArrayList(java.util.ArrayList) JsonNode(com.fasterxml.jackson.databind.JsonNode) GraphQLHttpRequest(io.github.microcks.util.graphql.GraphQLHttpRequest) JsonProcessingException(com.fasterxml.jackson.core.JsonProcessingException) ObjectNode(com.fasterxml.jackson.databind.node.ObjectNode) GraphQL(graphql.GraphQL) Resource(io.github.microcks.domain.Resource) Service(io.github.microcks.domain.Service) ExecutionResult(graphql.ExecutionResult) GraphQLSchema(graphql.schema.GraphQLSchema) JsonMappingException(io.github.microcks.util.dispatcher.JsonMappingException) JsonProcessingException(com.fasterxml.jackson.core.JsonProcessingException) ResponseEntity(org.springframework.http.ResponseEntity) Header(io.github.microcks.domain.Header) RequestMapping(org.springframework.web.bind.annotation.RequestMapping)

Example 12 with Header

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;
}
Also used : EventMessage(io.github.microcks.domain.EventMessage) Header(io.github.microcks.domain.Header)

Example 13 with Header

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;
}
Also used : Header(io.github.microcks.domain.Header) HashSet(java.util.HashSet)

Example 14 with Header

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();
    }
}
Also used : HashMap(java.util.HashMap) Channel(com.rabbitmq.client.Channel) IOException(java.io.IOException) Header(io.github.microcks.domain.Header) AMQP(com.rabbitmq.client.AMQP) TimeoutException(java.util.concurrent.TimeoutException)

Example 15 with Header

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);
}
Also used : Response(io.github.microcks.domain.Response) Header(io.github.microcks.domain.Header) FallbackSpecification(io.github.microcks.util.dispatcher.FallbackSpecification) HashMap(java.util.HashMap) JsonNode(com.fasterxml.jackson.databind.JsonNode) Operation(io.github.microcks.domain.Operation) JsonProcessingException(com.fasterxml.jackson.core.JsonProcessingException)

Aggregations

Header (io.github.microcks.domain.Header)19 JsonNode (com.fasterxml.jackson.databind.JsonNode)6 ArrayList (java.util.ArrayList)5 EventMessage (io.github.microcks.domain.EventMessage)4 Response (io.github.microcks.domain.Response)4 IOException (java.io.IOException)4 HashMap (java.util.HashMap)4 HashSet (java.util.HashSet)4 ArrayNode (com.fasterxml.jackson.databind.node.ArrayNode)3 ObjectNode (com.fasterxml.jackson.databind.node.ObjectNode)3 Operation (io.github.microcks.domain.Operation)3 Service (io.github.microcks.domain.Service)3 HttpHeaders (org.springframework.http.HttpHeaders)3 JsonProcessingException (com.fasterxml.jackson.core.JsonProcessingException)2 Request (io.github.microcks.domain.Request)2 Resource (io.github.microcks.domain.Resource)2 FallbackSpecification (io.github.microcks.util.dispatcher.FallbackSpecification)2 URI (java.net.URI)2 ResponseEntity (org.springframework.http.ResponseEntity)2 ClientHttpRequest (org.springframework.http.client.ClientHttpRequest)2