package br.com.conductor.heimdall.core.service; /*- * =========================LICENSE_START================================== * heimdall-core * ======================================================================== * Copyright (C) 2018 Conductor Tecnologia SA * ======================================================================== * 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. * ==========================LICENSE_END=================================== */ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import br.com.conductor.heimdall.core.entity.Api; import br.com.conductor.heimdall.core.repository.jdbc.OperationJDBCRepository; import br.com.conductor.heimdall.core.util.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; import org.springframework.data.domain.ExampleMatcher.StringMatcher; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import br.com.conductor.heimdall.core.converter.GenericConverter; import br.com.conductor.heimdall.core.dto.OperationDTO; import br.com.conductor.heimdall.core.dto.PageDTO; import br.com.conductor.heimdall.core.dto.PageableDTO; import br.com.conductor.heimdall.core.dto.page.OperationPage; import br.com.conductor.heimdall.core.entity.Operation; import br.com.conductor.heimdall.core.entity.Resource; import br.com.conductor.heimdall.core.exception.HeimdallException; import br.com.conductor.heimdall.core.repository.OperationRepository; import br.com.conductor.heimdall.core.repository.ResourceRepository; import br.com.conductor.heimdall.core.service.amqp.AMQPCacheService; import br.com.conductor.heimdall.core.service.amqp.AMQPRouteService; import br.com.conductor.heimdall.core.util.ConstantsCache; import br.com.conductor.heimdall.core.util.Pageable; import static br.com.conductor.heimdall.core.exception.ExceptionMessage.*; /** * This class provides methods to create, read, update and delete a {@link Operation} resource. * * @author Filipe Germano * @author Marcelo Aguiar Rodrigues * */ @Service public class OperationService { @Autowired private OperationRepository operationRepository; @Autowired private ResourceRepository resourceRepository; @Autowired private InterceptorService interceptorService; @Autowired private OperationJDBCRepository operationJDBCRepository; @Autowired private ApiService apiService; @Autowired private AMQPRouteService amqpRoute; @Autowired private AMQPCacheService amqpCacheService; /** * Finds a {@link Operation} by its Id, {@link Resource} Id and {@link br.com.conductor.heimdall.core.entity.Api} Id. * * @param apiId The {@link br.com.conductor.heimdall.core.entity.Api} Id * @param resourceId The {@link Resource} Id * @param operationId The {@link Operation} Id * @return The {@link Operation} found */ @Transactional(readOnly = true) public Operation find(Long apiId, Long resourceId, Long operationId) { Operation operation = operationRepository.findByResourceApiIdAndResourceIdAndId(apiId, resourceId, operationId); HeimdallException.checkThrow(operation == null, GLOBAL_RESOURCE_NOT_FOUND); return operation; } /** * Generates a paged list of {@link Operation} from a request. * * @param apiId The {@link br.com.conductor.heimdall.core.entity.Api} Id * @param resourceId The {@link Resource} Id * @param operationDTO The {@link OperationDTO} * @param pageableDTO The {@link PageableDTO} * @return The paged {@link Operation} list as a {@link OperationPage} object */ @Transactional(readOnly = true) public OperationPage list(Long apiId, Long resourceId, OperationDTO operationDTO, PageableDTO pageableDTO) { Example<Operation> example = this.prepareExample(apiId, resourceId, operationDTO); Pageable pageable = Pageable.setPageable(pageableDTO.getOffset(), pageableDTO.getLimit()); Page<Operation> page = operationRepository.findAll(example, pageable); return new OperationPage(PageDTO.build(page)); } /** * Generates a list of {@link Operation} from a request. * * @param apiId The {@link br.com.conductor.heimdall.core.entity.Api} Id * @param resourceId The {@link Resource} Id * @param operationDTO The {@link OperationDTO} * @return The list of {@link Operation} */ @Transactional(readOnly = true) public List<Operation> list(Long apiId, Long resourceId, OperationDTO operationDTO) { Example<Operation> example = this.prepareExample(apiId, resourceId, operationDTO); return operationRepository.findAll(example); } /* * Creates a Example for Hibernate query */ private Example<Operation> prepareExample(Long apiId, Long resourceId, OperationDTO operationDTO) { Resource resource = resourceRepository.findByApiIdAndId(apiId, resourceId); HeimdallException.checkThrow(resource == null, GLOBAL_RESOURCE_NOT_FOUND); Operation operation = GenericConverter.mapper(operationDTO, Operation.class); operation.setResource(resource); return Example.of(operation, ExampleMatcher.matching().withIgnorePaths("resource.api").withIgnoreCase().withStringMatcher(StringMatcher.CONTAINING)); } /** * Lists all {@link Operation} from one {@link Api} * * @param apiId The {@link Api} Id * @return The complete list of all {@link Operation} from the {@link Api} */ @Transactional(readOnly = true) public List<Operation> list(final Long apiId) { final Api api = apiService.find(apiId); final List<Operation> operations = new ArrayList<>(); final OperationDTO operationDTO = new OperationDTO(); api.getResources().forEach(resource -> operations.addAll(this.list(apiId, resource.getId(), operationDTO))); if (!operations.isEmpty()) { for (Operation operation : operations) { operation.setDescription(null); operation.setResource(null); } return operations; } return new ArrayList<>(); } /** * Saves a {@link Operation} to the repository. * * @param apiId The {@link br.com.conductor.heimdall.core.entity.Api} Id * @param resourceId The {@link Resource} Id * @return The saved {@link Operation} */ @Transactional public Operation save(Long apiId, Long resourceId, Operation operation) { Resource resource = resourceRepository.findByApiIdAndId(apiId, resourceId); HeimdallException.checkThrow(resource == null, GLOBAL_RESOURCE_NOT_FOUND); Operation resData = operationRepository.findByResourceApiIdAndMethodAndPath(apiId, operation.getMethod(), operation.getPath()); HeimdallException.checkThrow(resData != null && Objects.equals(resData.getResource().getId(), resource.getId()), ONLY_ONE_OPERATION_PER_RESOURCE); boolean patternExists = operationJDBCRepository.patternExists(resource.getApi().getBasePath() + "/" + operation.getPath(), apiId); HeimdallException.checkThrow(patternExists, OPERATION_ROUTE_ALREADY_EXISTS); operation.setResource(resource); operation.setPath(StringUtils.removeMultipleSlashes(operation.getPath())); HeimdallException.checkThrow(validateSingleWildCardOperationPath(operation), OPERATION_CANT_HAVE_SINGLE_WILDCARD); HeimdallException.checkThrow(validateDoubleWildCardOperationPath(operation), OPERATION_CANT_HAVE_DOUBLE_WILDCARD_NOT_AT_THE_END); operation = operationRepository.save(operation); amqpRoute.dispatchRoutes(); return operation; } /** * Updates a {@link Operation} by its Id, {@link br.com.conductor.heimdall.core.entity.Api} Id, {@link Resource} Id and {@link OperationDTO}. * * @param apiId The {@link br.com.conductor.heimdall.core.entity.Api} Id * @param resourceId The {@link Resource} Id * @param operationId The {@link Operation} Id * @param operationDTO The {@link OperationDTO} * @return The updated {@link Operation} */ @Transactional public Operation update(Long apiId, Long resourceId, Long operationId, OperationDTO operationDTO) { Operation operation = operationRepository.findByResourceApiIdAndResourceIdAndId(apiId, resourceId, operationId); HeimdallException.checkThrow(operation == null, GLOBAL_RESOURCE_NOT_FOUND); Operation resData = operationRepository.findByResourceApiIdAndMethodAndPath(apiId, operationDTO.getMethod(), operationDTO.getPath()); HeimdallException.checkThrow(resData != null && resData.getResource().getId().equals(operation.getResource().getId()) && !resData.getId().equals(operation.getId()), ONLY_ONE_OPERATION_PER_RESOURCE); operation = GenericConverter.mapper(operationDTO, operation); operation.setPath(StringUtils.removeMultipleSlashes(operation.getPath())); boolean patternExists = operationJDBCRepository.patternExists(operation.getResource().getApi().getBasePath() + "/" + operation.getPath(), apiId); HeimdallException.checkThrow(patternExists, OPERATION_ROUTE_ALREADY_EXISTS); HeimdallException.checkThrow(validateSingleWildCardOperationPath(operation), OPERATION_CANT_HAVE_SINGLE_WILDCARD); HeimdallException.checkThrow(validateDoubleWildCardOperationPath(operation), OPERATION_CANT_HAVE_DOUBLE_WILDCARD_NOT_AT_THE_END); operation = operationRepository.save(operation); amqpRoute.dispatchRoutes(); amqpCacheService.dispatchClean(ConstantsCache.OPERATION_ACTIVE_FROM_ENDPOINT, operation.getResource().getApi().getBasePath() + operation.getPath()); return operation; } /** * Deletes a {@link Operation} by its Id, {@link Resource} Id and {@link br.com.conductor.heimdall.core.entity.Api} Id. * * @param apiId The {@link br.com.conductor.heimdall.core.entity.Api} Id * @param resourceId The {@link Resource} Id * @param operationId The {@link Operation} Id */ @Transactional public void delete(Long apiId, Long resourceId, Long operationId) { Operation operation = operationRepository.findByResourceApiIdAndResourceIdAndId(apiId, resourceId, operationId); HeimdallException.checkThrow(operation == null, GLOBAL_RESOURCE_NOT_FOUND); // Deletes all interceptors attached to the Operation interceptorService.deleteAllfromOperation(operationId); operationRepository.delete(operation.getId()); amqpCacheService.dispatchClean(ConstantsCache.OPERATION_ACTIVE_FROM_ENDPOINT, operation.getResource().getApi().getBasePath() + operation.getPath()); amqpRoute.dispatchRoutes(); } /** * Deletes all Operations from a Resource * * @param apiId Api with the Resource * @param resourceId Resource with the Operations */ @Transactional public void deleteAllfromResource(Long apiId, Long resourceId) { List<Operation> operations = operationRepository.findByResourceApiIdAndResourceId(apiId, resourceId); operations.forEach(operation -> this.delete(apiId, resourceId, operation.getId())); } /* * A Operation can not have a single wild card at any point in it. * * @return true when the path of the operation contains a single wild card, false otherwise */ private boolean validateSingleWildCardOperationPath(Operation operation) { return Arrays.asList(operation.getPath().split("/")).contains("*"); } /* * A Operation can have a one double wild card that must to be at the end of it, not at any other point. * * @return true when the path has more than one double wild card or one not at the end, false otherwise */ private boolean validateDoubleWildCardOperationPath(Operation operation) { List<String> path = Arrays.asList(operation.getPath().split("/")); if (path.contains("**")) return !(operation.getPath().endsWith("**") && (path.stream().filter(o -> o.equals("**")).count() == 1)); else return false; } }