Search in sources :

Example 1 with FallbackSpecification

use of io.github.microcks.util.dispatcher.FallbackSpecification in project microcks by microcks.

the class RestController method execute.

@RequestMapping(value = "/{service}/{version}/**", method = { RequestMethod.HEAD, RequestMethod.OPTIONS, RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.PATCH, RequestMethod.DELETE })
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 mock response for service [{}, {}] on uri {} with verb {}", serviceName, version, request.getRequestURI(), request.getMethod());
    log.debug("Request body: {}", body);
    long startTime = System.currentTimeMillis();
    // Extract resourcePath for matching with correct operation.
    String requestURI = request.getRequestURI();
    String serviceAndVersion = null;
    String resourcePath = null;
    // Build the encoded URI fragment to retrieve simple resourcePath.
    serviceAndVersion = "/" + UriUtils.encodeFragment(serviceName, "UTF-8") + "/" + version;
    resourcePath = requestURI.substring(requestURI.indexOf(serviceAndVersion) + serviceAndVersion.length());
    // resourcePath = UriUtils.decode(resourcePath, "UTF-8");
    log.debug("Found resourcePath: {}", resourcePath);
    // If serviceName was encoded with '+' instead of '%20', remove them.
    if (serviceName.contains("+")) {
        serviceName = serviceName.replace('+', ' ');
    }
    // If resourcePath was encoded with '+' instead of '%20', replace them .
    if (resourcePath.contains("+")) {
        resourcePath = resourcePath.replace("+", "%20");
    }
    Service service = serviceRepository.findByNameAndVersion(serviceName, version);
    Operation rOperation = null;
    for (Operation operation : service.getOperations()) {
        // Select operation based onto Http verb (GET, POST, PUT, etc ...)
        if (operation.getMethod().equals(request.getMethod().toUpperCase())) {
            // ... then check is we have a matching resource path.
            if (operation.getResourcePaths() != null && operation.getResourcePaths().contains(resourcePath)) {
                rOperation = operation;
                break;
            }
        }
    }
    // using a Fallback dispatcher. Try again, just considering the verb and path pattern of operation.
    if (rOperation == null) {
        for (Operation operation : service.getOperations()) {
            // Select operation based onto Http verb (GET, POST, PUT, etc ...)
            if (operation.getMethod().equals(request.getMethod().toUpperCase())) {
                // ... then check is current resource path matches operation path pattern.
                if (operation.getResourcePaths() != null) {
                    // Produce a matching regexp removing {part} and :part from pattern.
                    String operationPattern = getURIPattern(operation.getName());
                    operationPattern = operationPattern.replaceAll("\\{.+\\}", "(.)+");
                    operationPattern = operationPattern.replaceAll("(/:[^:^/]+)", "\\/(.+)");
                    if (resourcePath.matches(operationPattern)) {
                        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) {
            return new ResponseEntity<Object>(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, getURIPattern(rOperation.getName()), UriUtils.decode(resourcePath, "UTF-8"), request, body);
        log.debug("Dispatch criteria for finding response is {}", dispatchCriteria);
        Response response = null;
        // Filter depending on requested media type.
        List<Response> responses = responseRepository.findByOperationIdAndDispatchCriteria(IdBuilder.buildOperationId(service, rOperation), dispatchCriteria);
        response = getResponseByMediaType(responses, request);
        if (response == null) {
            // When using the SCRIPT or JSON_BODY dispatchers, return of evaluation may be the name of response.
            responses = responseRepository.findByOperationIdAndName(IdBuilder.buildOperationId(service, rOperation), dispatchCriteria);
            response = getResponseByMediaType(responses, request);
        }
        if (response == null && fallback != null) {
            // If we've found nothing and got a fallback, that's the moment!
            responses = responseRepository.findByOperationIdAndName(IdBuilder.buildOperationId(service, rOperation), fallback.getFallback());
            response = getResponseByMediaType(responses, request);
        }
        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 = getResponseByMediaType(responses, request);
            }
        }
        if (response != null) {
            HttpStatus status = (response.getStatus() != null ? HttpStatus.valueOf(Integer.parseInt(response.getStatus())) : HttpStatus.OK);
            // Deal with specific headers (content-type and redirect directive).
            HttpHeaders responseHeaders = new HttpHeaders();
            if (response.getMediaType() != null) {
                responseHeaders.setContentType(MediaType.valueOf(response.getMediaType() + ";charset=UTF-8"));
            }
            // Deal with headers from parameter constraints if any?
            recopyHeadersFromParameterConstraints(rOperation, request, responseHeaders);
            // Adding other generic headers (caching directives and so on...)
            if (response.getHeaders() != null) {
                for (Header header : response.getHeaders()) {
                    if ("Location".equals(header.getName())) {
                        // We should process location in order to make relative URI specified an absolute one from
                        // the client perspective.
                        String location = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/rest" + serviceAndVersion + header.getValues().iterator().next();
                        responseHeaders.add(header.getName(), location);
                    } else {
                        if (!HttpHeaders.TRANSFER_ENCODING.equalsIgnoreCase(header.getName())) {
                            responseHeaders.put(header.getName(), new ArrayList<>(header.getValues()));
                        }
                    }
                }
            }
            // Render response content before waiting and returning.
            String responseContent = MockControllerCommons.renderResponseContent(body, resourcePath, request, response);
            // Setting delay to default one if not set.
            if (delay == null && rOperation.getDefaultDelay() != null) {
                delay = rOperation.getDefaultDelay();
            }
            MockControllerCommons.waitForDelay(startTime, delay);
            // Publish an invocation event before returning if enabled.
            if (enableInvocationStats) {
                MockControllerCommons.publishMockInvocation(applicationContext, this, service, response, startTime);
            }
            return new ResponseEntity<Object>(responseContent, responseHeaders, status);
        }
        return new ResponseEntity<Object>(HttpStatus.BAD_REQUEST);
    } else // Handle OPTIONS request if CORS policy is enabled.
    if (enableCorsPolicy && "OPTIONS".equals(request.getMethod().toUpperCase())) {
        log.debug("No valid operation found but Microcks configured to apply CORS policy");
        return handleCorsRequest(request);
    }
    log.debug("No valid operation found and Microcks configured to not apply CORS policy...");
    return new ResponseEntity<Object>(HttpStatus.NOT_FOUND);
}
Also used : Response(io.github.microcks.domain.Response) HttpHeaders(org.springframework.http.HttpHeaders) ResponseEntity(org.springframework.http.ResponseEntity) Header(io.github.microcks.domain.Header) FallbackSpecification(io.github.microcks.util.dispatcher.FallbackSpecification) HttpStatus(org.springframework.http.HttpStatus) Service(io.github.microcks.domain.Service) Operation(io.github.microcks.domain.Operation) RequestMapping(org.springframework.web.bind.annotation.RequestMapping)

Example 2 with FallbackSpecification

use of io.github.microcks.util.dispatcher.FallbackSpecification 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)

Example 3 with FallbackSpecification

use of io.github.microcks.util.dispatcher.FallbackSpecification in project microcks by microcks.

the class SoapController method execute.

@RequestMapping(value = "/{service}/{version}/**", method = RequestMethod.POST)
public ResponseEntity<?> execute(@PathVariable("service") String serviceName, @PathVariable("version") String version, @RequestParam(value = "validate", required = false) Boolean validate, @RequestParam(value = "delay", required = false) Long delay, @RequestBody String body, HttpServletRequest request) {
    log.info("Servicing mock response for service [{}, {}]", serviceName, version);
    log.debug("Request body: " + body);
    long startTime = System.currentTimeMillis();
    // If serviceName was encoded with '+' instead of '%20', replace them.
    if (serviceName.contains("+")) {
        serviceName = serviceName.replace('+', ' ');
    }
    log.info("Service name: " + serviceName);
    // Retrieve service and correct operation.
    Service service = serviceRepository.findByNameAndVersion(serviceName, version);
    Operation rOperation = null;
    // Enhancement : retrieve SOAPAction from request headers
    String action = extractSoapAction(request);
    log.debug("Extracted SOAP action from headers: {}", action);
    if (action != null && action.length() > 0) {
        for (Operation operation : service.getOperations()) {
            if (action.equals(operation.getAction())) {
                rOperation = operation;
                log.info("Found valid operation {}", rOperation.getName());
                break;
            }
        }
    }
    // Enhancement : if not found, try getting operation from soap:body directly!
    if (rOperation == null) {
        String operationName = extractOperationName(body);
        log.debug("Extracted operation name from payload: {}", operationName);
        if (operationName != null) {
            for (Operation operation : service.getOperations()) {
                if (operationName.equals(operation.getInputName()) || operationName.equals(operation.getName())) {
                    rOperation = operation;
                    log.info("Found valid operation {}", rOperation.getName());
                    break;
                }
            }
        }
    }
    // Now processing the request and send a response.
    if (rOperation != null) {
        log.debug("Found a valid operation with rules: {}", rOperation.getDispatcherRules());
        if (validate != null && validate) {
            log.debug("Soap message validation is turned on, validating...");
            try {
                List<XmlError> errors = SoapMessageValidator.validateSoapMessage(rOperation.getInputName(), service.getXmlNS(), body, resourceUrl + UriUtils.encodePath(service.getName() + "-" + version, "UTF-8") + ".wsdl", true);
                log.debug("SoapBody validation errors: " + errors.size());
                // Return a 400 http code with errors.
                if (errors != null && errors.size() > 0) {
                    return new ResponseEntity<Object>(errors, HttpStatus.BAD_REQUEST);
                }
            } catch (Exception e) {
                log.error("Error during Soap validation", e);
                return new ResponseEntity<Object>("Error during Soap validation: " + e.getMessage(), 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();
        }
        Response response = null;
        String dispatchCriteria = null;
        // Depending on dispatcher, evaluate request with rules.
        if (DispatchStyles.QUERY_MATCH.equals(dispatcher)) {
            dispatchCriteria = getDispatchCriteriaFromXPathEval(dispatcherRules, body);
        } else if (DispatchStyles.SCRIPT.equals(dispatcher)) {
            dispatchCriteria = getDispatchCriteriaFromScriptEval(dispatcherRules, body, request);
        }
        log.debug("Dispatch criteria for finding response is {}", dispatchCriteria);
        List<Response> responses = responseRepository.findByOperationIdAndDispatchCriteria(IdBuilder.buildOperationId(service, rOperation), dispatchCriteria);
        if (responses.isEmpty() && fallback != null) {
            // If we've found nothing and got a fallback, that's the moment!
            responses = responseRepository.findByOperationIdAndName(IdBuilder.buildOperationId(service, rOperation), fallback.getFallback());
        }
        if (!responses.isEmpty()) {
            response = responses.get(0);
        }
        // Set Content-Type to "text/xml".
        HttpHeaders responseHeaders = new HttpHeaders();
        // Check to see if we are processing a SOAP 1.2 request
        if (request.getContentType().startsWith("application/soap+xml")) {
            // we are; set Content-Type to "application/soap+xml"
            responseHeaders.setContentType(MediaType.valueOf("application/soap+xml;charset=UTF-8"));
        } else {
            // Set Content-Type to "text/xml".
            responseHeaders.setContentType(MediaType.valueOf("text/xml;charset=UTF-8"));
        }
        // Render response content before waiting and returning.
        String responseContent = MockControllerCommons.renderResponseContent(body, null, request, response);
        // Setting delay to default one if not set.
        if (delay == null && rOperation.getDefaultDelay() != null) {
            delay = rOperation.getDefaultDelay();
        }
        MockControllerCommons.waitForDelay(startTime, delay);
        // Publish an invocation event before returning if enabled.
        if (enableInvocationStats) {
            MockControllerCommons.publishMockInvocation(applicationContext, this, service, response, startTime);
        }
        if (response.isFault()) {
            return new ResponseEntity<Object>(responseContent, responseHeaders, HttpStatus.INTERNAL_SERVER_ERROR);
        }
        return new ResponseEntity<Object>(responseContent, responseHeaders, HttpStatus.OK);
    }
    log.debug("No valid operation found by Microcks...");
    return new ResponseEntity<Object>(HttpStatus.NOT_FOUND);
}
Also used : HttpHeaders(org.springframework.http.HttpHeaders) Service(io.github.microcks.domain.Service) XmlError(org.apache.xmlbeans.XmlError) Operation(io.github.microcks.domain.Operation) Response(io.github.microcks.domain.Response) ResponseEntity(org.springframework.http.ResponseEntity) FallbackSpecification(io.github.microcks.util.dispatcher.FallbackSpecification) RequestMapping(org.springframework.web.bind.annotation.RequestMapping)

Aggregations

Operation (io.github.microcks.domain.Operation)3 Response (io.github.microcks.domain.Response)3 FallbackSpecification (io.github.microcks.util.dispatcher.FallbackSpecification)3 Header (io.github.microcks.domain.Header)2 Service (io.github.microcks.domain.Service)2 HttpHeaders (org.springframework.http.HttpHeaders)2 ResponseEntity (org.springframework.http.ResponseEntity)2 RequestMapping (org.springframework.web.bind.annotation.RequestMapping)2 JsonProcessingException (com.fasterxml.jackson.core.JsonProcessingException)1 JsonNode (com.fasterxml.jackson.databind.JsonNode)1 HashMap (java.util.HashMap)1 XmlError (org.apache.xmlbeans.XmlError)1 HttpStatus (org.springframework.http.HttpStatus)1