All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.microcks.web.DynamicMockRestController Maven / Gradle / Ivy

The newest version!
/*
 * Copyright The Microcks Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.github.microcks.web;

import io.github.microcks.domain.GenericResource;
import io.github.microcks.domain.Operation;
import io.github.microcks.domain.Service;
import io.github.microcks.domain.ServiceType;
import io.github.microcks.event.MockInvocationEvent;
import io.github.microcks.repository.GenericResourceRepository;
import io.github.microcks.repository.ServiceRepository;
import io.github.microcks.util.SafeLogger;
import io.github.microcks.util.el.EvaluableRequest;
import io.github.microcks.util.el.TemplateEngine;
import io.github.microcks.util.el.TemplateEngineFactory;

import org.bson.Document;
import org.bson.json.JsonParseException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.util.UriUtils;

import jakarta.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * This is the controller for Dynamic mocks in Microcks.
 * @author laurent
 */
@org.springframework.web.bind.annotation.RestController
@RequestMapping("/dynarest")
public class DynamicMockRestController {

   /** A safe logger for filtering user-controlled data in diagnostic messages. */
   private static final SafeLogger log = SafeLogger.getLogger(DynamicMockRestController.class);

   public static final String ID_FIELD = "id";

   private final ServiceRepository serviceRepository;
   private final GenericResourceRepository genericResourceRepository;
   private final ApplicationContext applicationContext;

   @Value("${mocks.enable-invocation-stats}")
   private final Boolean enableInvocationStats = null;

   /**
    * Build a new DynamicMockRestController with required dependencies.
    * @param serviceRepository         the repository for services
    * @param genericResourceRepository the repository for generic resources
    * @param applicationContext        the Spring application context
    */
   public DynamicMockRestController(ServiceRepository serviceRepository,
         GenericResourceRepository genericResourceRepository, ApplicationContext applicationContext) {
      this.serviceRepository = serviceRepository;
      this.genericResourceRepository = genericResourceRepository;
      this.applicationContext = applicationContext;
   }

   @PostMapping(value = "/{service}/{version}/{resource}", produces = "application/json")
   public ResponseEntity createResource(@PathVariable("service") String serviceName,
         @PathVariable("version") String version, @PathVariable("resource") String resource,
         @RequestParam(value = "delay", required = false) Long delay, @RequestBody(required = true) String body,
         HttpServletRequest request) {
      log.debug("Creating a new resource '{}' for service '{}-{}'", resource, serviceName, version);
      long startTime = System.currentTimeMillis();

      serviceName = sanitizeServiceName(serviceName);

      MockContext mockContext = getMockContext(serviceName, version, "POST /" + resource);
      if (mockContext != null) {
         Document document = null;
         GenericResource genericResource = null;

         try {
            // Try parsing body payload that should be json.
            document = Document.parse(body);
            // Now create a generic resource.
            genericResource = new GenericResource();
            genericResource.setServiceId(mockContext.service.getId());
            genericResource.setPayload(document);

            genericResource = genericResourceRepository.save(genericResource);
         } catch (JsonParseException jpe) {
            // Return a 422 code : unprocessable entity.
            return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY);
         }

         // Append id and wait if specified before returning.
         document.append(ID_FIELD, genericResource.getId());
         waitForDelay(startTime, delay, mockContext);
         return new ResponseEntity<>(document.toJson(), HttpStatus.CREATED);
      }
      // Return a 400 code : bad request.
      return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
   }

   @GetMapping(value = "/{service}/{version}/{resource}", produces = "application/json")
   public ResponseEntity findResources(@PathVariable("service") String serviceName,
         @PathVariable("version") String version, @PathVariable("resource") String resource,
         @RequestParam(value = "page", required = false, defaultValue = "0") int page,
         @RequestParam(value = "size", required = false, defaultValue = "20") int size,
         @RequestParam(value = "delay", required = false) Long delay, @RequestBody(required = false) String body,
         HttpServletRequest request) {
      log.debug("Find resources '{}' for service '{}-{}'", resource, serviceName, version);
      long startTime = System.currentTimeMillis();

      serviceName = sanitizeServiceName(serviceName);

      // Build the encoded URI fragment to retrieve simple resourcePath.
      String requestURI = request.getRequestURI();
      String serviceAndVersion = "/" + UriUtils.encodeFragment(serviceName, "UTF-8") + "/" + version;
      String resourcePath = requestURI.substring(requestURI.indexOf(serviceAndVersion) + serviceAndVersion.length());

      MockContext mockContext = getMockContext(serviceName, version, "GET /" + resource);
      if (mockContext != null) {

         List genericResources = null;
         if (body == null) {
            genericResources = genericResourceRepository.findByServiceId(mockContext.service.getId(),
                  PageRequest.of(page, size));
         } else {
            genericResources = genericResourceRepository.findByServiceIdAndJSONQuery(mockContext.service.getId(), body);
         }

         // Prepare templating support with evaluable request and engine.
         EvaluableRequest evaluableRequest = MockControllerCommons.buildEvaluableRequest(body, resourcePath, request);
         TemplateEngine engine = TemplateEngineFactory.getTemplateEngine();

         // Transform and collect resources.
         List resources = genericResources.stream().map(genericResource -> MockControllerCommons
               .renderResponseContent(evaluableRequest, engine, transformToResourceJSON(genericResource)))
               .collect(Collectors.toList());

         // Wait if specified before returning.
         waitForDelay(startTime, delay, mockContext);
         MockControllerCommons.waitForDelay(startTime, delay);

         return new ResponseEntity<>(formatToJSONArray(resources), HttpStatus.OK);
      }
      // Return a 400 code : bad request.
      return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
   }

   @GetMapping(value = "/{service}/{version}/{resource}/{resourceId}", produces = "application/json")
   public ResponseEntity getResource(@PathVariable("service") String serviceName,
         @PathVariable("version") String version, @PathVariable("resource") String resource,
         @PathVariable("resourceId") String resourceId, @RequestParam(value = "delay", required = false) Long delay,
         HttpServletRequest request) {
      log.debug("Get resource '{}:{}' for service '{}-{}'", resource, resourceId, serviceName, version);
      long startTime = System.currentTimeMillis();

      serviceName = sanitizeServiceName(serviceName);

      // Build the encoded URI fragment to retrieve simple resourcePath.
      String requestURI = request.getRequestURI();
      String serviceAndVersion = "/" + UriUtils.encodeFragment(serviceName, "UTF-8") + "/" + version;
      String resourcePath = requestURI.substring(requestURI.indexOf(serviceAndVersion) + serviceAndVersion.length());

      MockContext mockContext = getMockContext(serviceName, version, "GET /" + resource + "/:id");
      if (mockContext != null) {
         // Get the requested generic resource.
         GenericResource genericResource = genericResourceRepository.findById(resourceId).orElse(null);

         // Wait if specified before returning.
         waitForDelay(startTime, delay, mockContext);

         if (genericResource != null) {
            // Prepare templating support with evaluable request and engine.
            EvaluableRequest evaluableRequest = MockControllerCommons.buildEvaluableRequest(null, resourcePath,
                  request);
            TemplateEngine engine = TemplateEngineFactory.getTemplateEngine();

            // Return the resource as well as a 200 code.
            return new ResponseEntity<>(MockControllerCommons.renderResponseContent(evaluableRequest, engine,
                  transformToResourceJSON(genericResource)), HttpStatus.OK);
         } else {
            // Return a 404 code : not found.
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
         }
      }

      // Return a 400 code : bad request.
      return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
   }

   @PutMapping(value = "/{service}/{version}/{resource}/{resourceId}", produces = "application/json")
   public ResponseEntity updateResource(@PathVariable("service") String serviceName,
         @PathVariable("version") String version, @PathVariable("resource") String resource,
         @PathVariable("resourceId") String resourceId, @RequestParam(value = "delay", required = false) Long delay,
         @RequestBody(required = true) String body, HttpServletRequest request) {
      log.debug("Update resource '{}:{}' for service '{}-{}'", resource, resourceId, serviceName, version);
      long startTime = System.currentTimeMillis();

      serviceName = sanitizeServiceName(serviceName);

      MockContext mockContext = getMockContext(serviceName, version, "PUT /" + resource + "/:id");
      if (mockContext != null) {
         // Get the requested generic resource.
         GenericResource genericResource = genericResourceRepository.findById(resourceId).orElse(null);
         if (genericResource != null) {
            Document document = null;

            try {
               // Try parsing body payload that should be json.
               document = Document.parse(body);
               document.remove(ID_FIELD);

               // Now update the generic resource payload.
               genericResource.setPayload(document);

               genericResourceRepository.save(genericResource);
            } catch (JsonParseException jpe) {
               // Return a 422 code : unprocessable entity.
               return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY);
            }
            // Wait if specified before returning.
            waitForDelay(startTime, delay, mockContext);

            // Return the updated resource as well as a 200 code.
            return new ResponseEntity<>(transformToResourceJSON(genericResource), HttpStatus.OK);

         } else {
            // Wait if specified before returning.
            waitForDelay(startTime, delay, mockContext);

            // Return a 404 code : not found.
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
         }
      }

      // Return a 400 code : bad request.
      return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
   }

   @DeleteMapping(value = "/{service}/{version}/{resource}/{resourceId}")
   public ResponseEntity deleteResource(@PathVariable("service") String serviceName,
         @PathVariable("version") String version, @PathVariable("resource") String resource,
         @PathVariable("resourceId") String resourceId, @RequestParam(value = "delay", required = false) Long delay) {
      log.debug("Update resource '{}:{}' for service '{}-{}'", resource, resourceId, serviceName, version);
      long startTime = System.currentTimeMillis();

      serviceName = sanitizeServiceName(serviceName);

      MockContext mockContext = getMockContext(serviceName, version, "DELETE /" + resource + "/:id");
      if (mockContext != null) {
         genericResourceRepository.deleteById(resourceId);

         // Wait if specified before returning.
         waitForDelay(startTime, delay, mockContext);

         // Return a 204 code : done and no content returned.
         return new ResponseEntity<>(HttpStatus.NO_CONTENT);
      }

      // Return a 400 code : bad request.
      return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
   }

   /** Sanitize the service name (check encoding and so on...) */
   private String sanitizeServiceName(String serviceName) {
      // If serviceName was encoded with '+' instead of '%20', replace them.
      if (serviceName.contains("+")) {
         return serviceName.replace('+', ' ');
      }
      return serviceName;
   }

   /** Retrieve a MockContext corresponding to operation on service. Null if not found or not valid. */
   private MockContext getMockContext(String serviceName, String version, String operationName) {
      Service service = serviceRepository.findByNameAndVersion(serviceName, version);
      if (service != null && ServiceType.GENERIC_REST.equals(service.getType())) {
         for (Operation operation : service.getOperations()) {
            if (operationName.equals(operation.getName())) {
               return new MockContext(service, operation);
            }
         }
      }
      return null;
   }

   private String transformToResourceJSON(GenericResource genericResource) {
      Document document = genericResource.getPayload();
      document.append(ID_FIELD, genericResource.getId());
      return document.toJson();
   }

   private String formatToJSONArray(List resources) {
      StringBuilder builder = new StringBuilder("[");
      for (int i = 0; i < resources.size(); i++) {
         builder.append(resources.get(i));
         if (i < resources.size() - 1) {
            builder.append(", ");
         }
      }
      return builder.append("]").toString();
   }

   private void waitForDelay(Long since, Long delay, MockContext mockContext) {
      // Setting delay to default one if not set.
      if (delay == null && mockContext.operation.getDefaultDelay() != null) {
         delay = mockContext.operation.getDefaultDelay();
      }

      MockControllerCommons.waitForDelay(since, delay);

      // Publish an invocation event before returning if enabled.
      if (Boolean.TRUE.equals(enableInvocationStats)) {
         MockInvocationEvent event = new MockInvocationEvent(this, mockContext.service.getName(),
               mockContext.service.getVersion(), "DynamicMockRestController", new Date(since),
               since - System.currentTimeMillis());
         applicationContext.publishEvent(event);
         log.debug("Mock invocation event has been published");
      }
   }

   private class MockContext {
      public Service service;
      public Operation operation;

      public MockContext(Service service, Operation operation) {
         this.service = service;
         this.operation = operation;
      }
   }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy