Search in sources :

Example 1 with Operation

use of io.github.microcks.domain.Operation 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 Operation

use of io.github.microcks.domain.Operation in project microcks by microcks.

the class ServiceServiceTest method testCreateGenericResourceService.

@Test
public void testCreateGenericResourceService() {
    Service created = null;
    try {
        created = service.createGenericResourceService("Order Service", "1.0", "order");
    } catch (Exception e) {
        fail("No exception should be thrown");
    }
    // Check created object.
    assertNotNull(created.getId());
    // Retrieve object by id and assert on what has been persisted.
    Service retrieved = repository.findById(created.getId()).orElse(null);
    assertEquals("Order Service", retrieved.getName());
    assertEquals("1.0", retrieved.getVersion());
    assertEquals(ServiceType.GENERIC_REST, retrieved.getType());
    // Now check operations.
    assertEquals(5, retrieved.getOperations().size());
    for (Operation op : retrieved.getOperations()) {
        if ("POST /order".equals(op.getName())) {
            assertEquals("POST", op.getMethod());
        } else if ("GET /order/:id".equals(op.getName())) {
            assertEquals("GET", op.getMethod());
            assertEquals(DispatchStyles.URI_PARTS, op.getDispatcher());
            assertEquals("id", op.getDispatcherRules());
        } else if ("GET /order".equals(op.getName())) {
            assertEquals("GET", op.getMethod());
        } else if ("PUT /order/:id".equals(op.getName())) {
            assertEquals("PUT", op.getMethod());
            assertEquals(DispatchStyles.URI_PARTS, op.getDispatcher());
            assertEquals("id", op.getDispatcherRules());
        } else if ("DELETE /order/:id".equals(op.getName())) {
            assertEquals("DELETE", op.getMethod());
            assertEquals(DispatchStyles.URI_PARTS, op.getDispatcher());
            assertEquals("id", op.getDispatcherRules());
        } else {
            fail("Unknown operation name: " + op.getName());
        }
    }
}
Also used : Service(io.github.microcks.domain.Service) Operation(io.github.microcks.domain.Operation) EntityAlreadyExistsException(io.github.microcks.util.EntityAlreadyExistsException) MockRepositoryImportException(io.github.microcks.util.MockRepositoryImportException) Test(org.junit.Test)

Example 3 with Operation

use of io.github.microcks.domain.Operation in project microcks by microcks.

the class GraphQLImporterTest method testSimpleGraphQLImport.

@Test
public void testSimpleGraphQLImport() {
    GraphQLImporter importer = null;
    try {
        importer = new GraphQLImporter("target/test-classes/io/github/microcks/util/graphql/films.graphql");
    } catch (IOException ioe) {
        fail("Exception should not be thrown");
    }
    // Check that basic service properties are there.
    List<Service> services = null;
    try {
        services = importer.getServiceDefinitions();
    } catch (MockRepositoryImportException e) {
        fail("Service definition import should not fail");
    }
    assertEquals(1, services.size());
    Service service = services.get(0);
    assertEquals("Movie Graph API", service.getName());
    assertEquals(ServiceType.GRAPHQL, service.getType());
    assertEquals("1.0", service.getVersion());
    // Check that resources have been parsed, correctly renamed, etc...
    List<Resource> resources = null;
    try {
        resources = importer.getResourceDefinitions(service);
    } catch (MockRepositoryImportException mrie) {
        fail("Resource definition import should not fail");
    }
    assertEquals(1, resources.size());
    Resource resource = resources.get(0);
    assertEquals(ResourceType.GRAPHQL_SCHEMA, resource.getType());
    assertEquals("Movie Graph API-1.0.graphql", resource.getName());
    // Check that operations and input/output have been found.
    assertEquals(4, service.getOperations().size());
    for (Operation operation : service.getOperations()) {
        if ("allFilms".equals(operation.getName())) {
            assertEquals("QUERY", operation.getMethod());
            assertEquals("FilmsConnection", operation.getOutputName());
            assertNull(operation.getDispatcher());
        } else if ("film".equals(operation.getName())) {
            assertEquals("QUERY", operation.getMethod());
            assertEquals("Film", operation.getOutputName());
            assertEquals("String", operation.getInputName());
            assertEquals(DispatchStyles.QUERY_ARGS, operation.getDispatcher());
            assertEquals("id", operation.getDispatcherRules());
        } else if ("addStar".equals(operation.getName())) {
            assertEquals("MUTATION", operation.getMethod());
            assertEquals("Film", operation.getOutputName());
            assertEquals("String", operation.getInputName());
            assertEquals(DispatchStyles.QUERY_ARGS, operation.getDispatcher());
            assertEquals("filmId", operation.getDispatcherRules());
        } else if ("addReview".equals(operation.getName())) {
            assertEquals("MUTATION", operation.getMethod());
            assertEquals("Film", operation.getOutputName());
            assertEquals("String, Review", operation.getInputName());
            assertNull(operation.getDispatcher());
        } else {
            fail("Unknown operation");
        }
    }
}
Also used : Resource(io.github.microcks.domain.Resource) Service(io.github.microcks.domain.Service) IOException(java.io.IOException) Operation(io.github.microcks.domain.Operation) MockRepositoryImportException(io.github.microcks.util.MockRepositoryImportException) Test(org.junit.Test)

Example 4 with Operation

use of io.github.microcks.domain.Operation in project microcks by microcks.

the class AsyncMinionApp method onStart.

/**
 * Application startup method.
 */
void onStart(@Observes StartupEvent ev) {
    // We need to retrieve Keycloak server from Microcks config.
    KeycloakConfig config = microcksAPIConnector.getKeycloakConfig();
    logger.infof("Microcks Keycloak server url {%s} and realm {%s}", config.getAuthServerUrl(), config.getRealm());
    String keycloakEndpoint = config.getAuthServerUrl() + "/realms/" + config.getRealm() + "/protocol/openid-connect/token";
    if (!keycloakAuthURL.isEmpty() && keycloakAuthURL.get().length() > 0) {
        logger.infof("Use locally defined Keycloak Auth URL: %s", keycloakAuthURL);
        keycloakEndpoint = keycloakAuthURL.get() + "/realms/" + config.getRealm() + "/protocol/openid-connect/token";
    }
    try {
        // First retrieve an authentication token before fetching async messages to publish.
        String oauthToken;
        if (config.isEnabled()) {
            // We've got a full Keycloak config, attempt an authent.
            oauthToken = keycloakConnector.connectAndGetOAuthToken(keycloakEndpoint);
            logger.info("Authentication to Keycloak server succeed!");
        } else {
            // No realm config, probably a dev mode - use a fake token.
            oauthToken = "<anonymous-admin-token>";
            logger.info("Keycloak protection is not enabled, using a fake token");
        }
        int page = 0;
        boolean fetchServices = true;
        while (fetchServices) {
            List<Service> services = microcksAPIConnector.listServices("Bearer " + oauthToken, page, SERVICES_FETCH_SIZE);
            for (Service service : services) {
                logger.debug("Found service " + service.getName() + " - " + service.getVersion());
                if (service.getType().equals(ServiceType.EVENT)) {
                    // Find the operations matching this minion constraints..
                    List<Operation> operations = service.getOperations().stream().filter(o -> Arrays.asList(restrictedFrequencies).contains(o.getDefaultDelay())).filter(o -> o.getBindings().keySet().stream().anyMatch(Arrays.asList(supportedBindings)::contains)).collect(Collectors.toList());
                    if (operations.size() > 0) {
                        logger.info("Found " + operations.size() + " candidate operations in " + service.getName() + " - " + service.getVersion());
                        ServiceView serviceView = microcksAPIConnector.getService("Bearer " + oauthToken, service.getId(), true);
                        for (Operation operation : operations) {
                            AsyncMockDefinition mockDefinition = new AsyncMockDefinition(serviceView.getService(), operation, serviceView.getMessagesMap().get(operation.getName()).stream().filter(e -> e instanceof UnidirectionalEvent).map(e -> ((UnidirectionalEvent) e).getEventMessage()).collect(Collectors.toList()));
                            mockRepository.storeMockDefinition(mockDefinition);
                            schemaRegistry.updateRegistryForService(mockDefinition.getOwnerService());
                        }
                    }
                }
            }
            if (services.size() < SERVICES_FETCH_SIZE) {
                fetchServices = false;
            }
            page++;
        }
        logger.info("Starting scheduling of all producer jobs...");
        producerScheduler.scheduleAllProducerJobs();
    } catch (ConnectorException ce) {
        logger.error("Cannot authenticate to Keycloak server and thus enable to call Microcks API" + "to get Async APIs to mocks...", ce);
        throw new RuntimeException("Unable to start the Minion due to connection exception");
    } catch (IOException ioe) {
        logger.error("IOException while communicating with Keycloak or Microcks API", ioe);
        throw new RuntimeException("Unable to start the Minion due to IO exception");
    }
}
Also used : KeycloakConnector(io.github.microcks.minion.async.client.KeycloakConnector) Arrays(java.util.Arrays) RestClient(org.eclipse.microprofile.rest.client.inject.RestClient) Logger(org.jboss.logging.Logger) ServiceView(io.github.microcks.domain.ServiceView) MicrocksAPIConnector(io.github.microcks.minion.async.client.MicrocksAPIConnector) IOException(java.io.IOException) UnidirectionalEvent(io.github.microcks.domain.UnidirectionalEvent) ConnectorException(io.github.microcks.minion.async.client.ConnectorException) Collectors(java.util.stream.Collectors) KeycloakConfig(io.github.microcks.minion.async.client.KeycloakConfig) Inject(javax.inject.Inject) Service(io.github.microcks.domain.Service) List(java.util.List) Operation(io.github.microcks.domain.Operation) ServiceType(io.github.microcks.domain.ServiceType) Observes(javax.enterprise.event.Observes) Optional(java.util.Optional) ApplicationScoped(javax.enterprise.context.ApplicationScoped) StartupEvent(io.quarkus.runtime.StartupEvent) ConfigProperty(org.eclipse.microprofile.config.inject.ConfigProperty) ServiceView(io.github.microcks.domain.ServiceView) UnidirectionalEvent(io.github.microcks.domain.UnidirectionalEvent) Service(io.github.microcks.domain.Service) Operation(io.github.microcks.domain.Operation) IOException(java.io.IOException) ConnectorException(io.github.microcks.minion.async.client.ConnectorException) KeycloakConfig(io.github.microcks.minion.async.client.KeycloakConfig)

Example 5 with Operation

use of io.github.microcks.domain.Operation in project microcks by microcks.

the class ServiceChangeEventPublisher method onApplicationEvent.

@Override
@Async
public void onApplicationEvent(ServiceChangeEvent event) {
    log.debug("Received a ServiceChangeEvent on " + event.getServiceId());
    ServiceView serviceView = null;
    if (event.getChangeType() != ChangeType.DELETED) {
        Service service = serviceRepository.findById(event.getServiceId()).orElse(null);
        if (service != null) {
            // Put messages into a map where key is operation name.
            Map<String, List<? extends Exchange>> messagesMap = new HashMap<>();
            for (Operation operation : service.getOperations()) {
                if (service.getType() == ServiceType.EVENT) {
                    // If an event, we should explicitly retrieve event messages.
                    List<UnidirectionalEvent> events = messageService.getEventByOperation(IdBuilder.buildOperationId(service, operation));
                    messagesMap.put(operation.getName(), events);
                } else {
                    // Otherwise we have traditional request / response pairs.
                    List<RequestResponsePair> pairs = messageService.getRequestResponseByOperation(IdBuilder.buildOperationId(service, operation));
                    messagesMap.put(operation.getName(), pairs);
                }
            }
            serviceView = new ServiceView(service, messagesMap);
        }
    }
    // Build and send a ServiceViewChangeEvent that wraps ServiceView.
    ServiceViewChangeEvent serviceViewChangeEvent = new ServiceViewChangeEvent(event.getServiceId(), serviceView, event.getChangeType(), System.currentTimeMillis());
    kafkaTemplate.send("microcks-services-updates", event.getServiceId(), serviceViewChangeEvent);
    log.debug("Processing of ServiceChangeEvent done !");
}
Also used : RequestResponsePair(io.github.microcks.domain.RequestResponsePair) ServiceView(io.github.microcks.domain.ServiceView) ServiceViewChangeEvent(io.github.microcks.event.ServiceViewChangeEvent) HashMap(java.util.HashMap) UnidirectionalEvent(io.github.microcks.domain.UnidirectionalEvent) Service(io.github.microcks.domain.Service) MessageService(io.github.microcks.service.MessageService) Operation(io.github.microcks.domain.Operation) Exchange(io.github.microcks.domain.Exchange) List(java.util.List) Async(org.springframework.scheduling.annotation.Async)

Aggregations

Operation (io.github.microcks.domain.Operation)26 Service (io.github.microcks.domain.Service)14 JsonNode (com.fasterxml.jackson.databind.JsonNode)7 IOException (java.io.IOException)7 ArrayList (java.util.ArrayList)7 MockRepositoryImportException (io.github.microcks.util.MockRepositoryImportException)6 Resource (io.github.microcks.domain.Resource)5 UnidirectionalEvent (io.github.microcks.domain.UnidirectionalEvent)5 Exchange (io.github.microcks.domain.Exchange)4 RequestResponsePair (io.github.microcks.domain.RequestResponsePair)4 Response (io.github.microcks.domain.Response)4 ServiceType (io.github.microcks.domain.ServiceType)4 Test (org.junit.Test)4 Header (io.github.microcks.domain.Header)3 ServiceView (io.github.microcks.domain.ServiceView)3 List (java.util.List)3 Collectors (java.util.stream.Collectors)3 FieldDefinition (graphql.language.FieldDefinition)2 InputValueDefinition (graphql.language.InputValueDefinition)2 Metadata (io.github.microcks.domain.Metadata)2