diff --git a/gravitee-apim-bom/pom.xml b/gravitee-apim-bom/pom.xml index a607903120b..38327c13916 100644 --- a/gravitee-apim-bom/pom.xml +++ b/gravitee-apim-bom/pom.xml @@ -736,4 +736,4 @@ - \ No newline at end of file + diff --git a/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/flow/AbstractFlow.java b/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/flow/AbstractFlow.java index 88665e1b969..efa865f542b 100644 --- a/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/flow/AbstractFlow.java +++ b/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/flow/AbstractFlow.java @@ -20,10 +20,10 @@ import io.gravitee.definition.model.v4.flow.step.Step; import jakarta.validation.constraints.NotEmpty; import java.io.Serializable; +import java.util.Collection; import java.util.List; -import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; +import java.util.stream.Stream; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.EqualsAndHashCode; @@ -55,9 +55,12 @@ public abstract class AbstractFlow implements Serializable { @JsonIgnore protected List computePlugins(List step) { - return Optional + return Stream .ofNullable(step) - .map(r -> r.stream().filter(Step::isEnabled).map(Step::getPlugins).flatMap(List::stream).collect(Collectors.toList())) - .orElse(List.of()); + .flatMap(Collection::stream) + .filter(Step::isEnabled) + .map(Step::getPlugins) + .flatMap(List::stream) + .toList(); } } diff --git a/gravitee-apim-repository/gravitee-apim-repository-api/src/main/java/io/gravitee/repository/management/api/MembershipRepository.java b/gravitee-apim-repository/gravitee-apim-repository-api/src/main/java/io/gravitee/repository/management/api/MembershipRepository.java index d2adf4a2aef..a94b4ad5685 100644 --- a/gravitee-apim-repository/gravitee-apim-repository-api/src/main/java/io/gravitee/repository/management/api/MembershipRepository.java +++ b/gravitee-apim-repository/gravitee-apim-repository-api/src/main/java/io/gravitee/repository/management/api/MembershipRepository.java @@ -41,6 +41,8 @@ public interface MembershipRepository extends FindAllRepository { */ List deleteByReferenceIdAndReferenceType(String referenceId, MembershipReferenceType referenceType) throws TechnicalException; + List findByReferenceIdAndReferenceType(String referenceId, MembershipReferenceType referenceType) throws TechnicalException; + /** * find membership by id. * @param membershipId the membership id diff --git a/gravitee-apim-repository/gravitee-apim-repository-jdbc/src/main/java/io/gravitee/repository/jdbc/management/JdbcMembershipRepository.java b/gravitee-apim-repository/gravitee-apim-repository-jdbc/src/main/java/io/gravitee/repository/jdbc/management/JdbcMembershipRepository.java index 8e8703a7b00..83140684ec6 100644 --- a/gravitee-apim-repository/gravitee-apim-repository-jdbc/src/main/java/io/gravitee/repository/jdbc/management/JdbcMembershipRepository.java +++ b/gravitee-apim-repository/gravitee-apim-repository-jdbc/src/main/java/io/gravitee/repository/jdbc/management/JdbcMembershipRepository.java @@ -77,7 +77,7 @@ public List deleteByReferenceIdAndReferenceType(String referenceId, Memb LOGGER.debug("JdbcMembershipRepository.deleteByReferenceIdAndReferenceType({}, {})", referenceId, referenceType); try { List rows = jdbcTemplate.queryForList( - "select id from " + this.tableName + " where reference_type = ?" + " and reference_id = ?", + "select id from " + this.tableName + " where reference_type = ? and reference_id = ?", String.class, referenceType.name(), referenceId @@ -85,7 +85,7 @@ public List deleteByReferenceIdAndReferenceType(String referenceId, Memb if (!rows.isEmpty()) { jdbcTemplate.update( - "delete from " + this.tableName + " where reference_type = ?" + " and reference_id = ?", + "delete from " + this.tableName + " where reference_type = ? and reference_id = ?", referenceType.name(), referenceId ); @@ -98,6 +98,28 @@ public List deleteByReferenceIdAndReferenceType(String referenceId, Memb } } + @Override + public List findByReferenceIdAndReferenceType(String referenceId, MembershipReferenceType referenceType) + throws TechnicalException { + LOGGER.debug("JdbcMembershipRepository.findByReferenceIdAndReferenceType({}, {})", referenceId, referenceType); + try { + return jdbcTemplate.query( + """ + %s + where reference_type = '%s' and reference_id = '%s' + """.formatted( + getOrm().getSelectAllSql(), + referenceType.name(), + referenceId + ), + getOrm().getRowMapper() + ); + } catch (final Exception ex) { + LOGGER.error("Failed to find memberships for refId: {}/{}", referenceId, referenceType, ex); + throw new TechnicalException("Failed to find memberships by reference", ex); + } + } + @Override public Set findByIds(Set membershipIds) throws TechnicalException { LOGGER.debug("JdbcMembershipRepository.findByIds({})", membershipIds); @@ -127,7 +149,7 @@ public Set findByReferenceAndRoleId(MembershipReferenceType referenc } @Override - public Set findByRoleId(String roleId) throws TechnicalException { + public Set findByRoleId(String roleId) { LOGGER.debug("JdbcMembershipRepository.findByRoleId({})", roleId); final StringBuilder query = new StringBuilder("select m.* from " + this.tableName + " m"); initializeWhereClause(null, null, roleId, query); diff --git a/gravitee-apim-repository/gravitee-apim-repository-mongodb/src/main/java/io/gravitee/repository/mongodb/management/MongoMembershipRepository.java b/gravitee-apim-repository/gravitee-apim-repository-mongodb/src/main/java/io/gravitee/repository/mongodb/management/MongoMembershipRepository.java index cefbf27cf12..d3658e27a85 100644 --- a/gravitee-apim-repository/gravitee-apim-repository-mongodb/src/main/java/io/gravitee/repository/mongodb/management/MongoMembershipRepository.java +++ b/gravitee-apim-repository/gravitee-apim-repository-mongodb/src/main/java/io/gravitee/repository/mongodb/management/MongoMembershipRepository.java @@ -15,6 +15,8 @@ */ package io.gravitee.repository.mongodb.management; +import static io.gravitee.repository.mongodb.utils.CollectionUtils.stream; + import io.gravitee.repository.exceptions.TechnicalException; import io.gravitee.repository.management.api.MembershipRepository; import io.gravitee.repository.management.model.Membership; @@ -117,6 +119,20 @@ public List deleteByReferenceIdAndReferenceType(String referenceId, Memb } } + @Override + public List findByReferenceIdAndReferenceType(String referenceId, MembershipReferenceType referenceType) + throws TechnicalException { + logger.debug("Find memberships by reference [{}/{}]", referenceId, referenceType); + try { + return stream(internalMembershipRepo.findReferenceIdAndReferenceType(referenceId, referenceType.name())) + .map(this::map) + .toList(); + } catch (Exception ex) { + logger.error("Failed to find memberships by ref: {}/{}", referenceId, referenceType, ex); + throw new TechnicalException("Failed to find memberships by ref"); + } + } + @Override public Optional findById(String membershipId) throws TechnicalException { logger.debug("Find membership by ID [{}]", membershipId); @@ -186,7 +202,7 @@ public Stream findRefIdsByMemberIdAndMemberTypeAndReferenceType( String memberId, MembershipMemberType memberType, MembershipReferenceType referenceType - ) throws TechnicalException { + ) { return internalMembershipRepo.findRefIdsByMemberIdAndMemberTypeAndReferenceType(memberId, memberType.name(), referenceType.name()); } @@ -297,9 +313,9 @@ public Set findByMemberIdAndMemberTypeAndReferenceTypeAndRoleIdIn( MembershipMemberType memberType, MembershipReferenceType referenceType, Collection roleIds - ) throws TechnicalException { + ) { logger.debug( - "Find membership by user and referenceType and referenceId and roleId in [{}, {}, {}, {}, {}]", + "Find membership by user and referenceType and referenceId and roleId in [{}, {}, {}, {}]", memberId, memberType, referenceType, @@ -311,7 +327,7 @@ public Set findByMemberIdAndMemberTypeAndReferenceTypeAndRoleIdIn( .map(this::map) .collect(Collectors.toSet()); logger.debug( - "Find membership by user and referenceType and referenceId, roleId in [{}, {}, {}, {}, {}] = {}", + "Find membership by user and referenceType and referenceId, roleId in [{}, {}, {}, {}] = {}", memberId, memberType, referenceType, @@ -327,9 +343,9 @@ public Set findRefIdByMemberAndRefTypeAndRoleIdIn( MembershipMemberType memberType, MembershipReferenceType referenceType, Collection roleIds - ) throws TechnicalException { + ) { logger.debug( - "Find membership by user and referenceType and referenceId and roleId in [{}, {}, {}, {}, {}]", + "Find membership by user and referenceType and referenceId and roleId in [{}, {}, {}, {}]", memberId, memberType, referenceType, @@ -341,7 +357,7 @@ public Set findRefIdByMemberAndRefTypeAndRoleIdIn( .map(MembershipMongo::getReferenceId) .collect(Collectors.toSet()); logger.debug( - "Find membership by user and referenceType and referenceId, roleId in [{}, {}, {}, {}, {}] = {}", + "Find membership by user and referenceType and referenceId, roleId in [{}, {}, {}, {}] = {}", memberId, memberType, referenceType, diff --git a/gravitee-apim-repository/gravitee-apim-repository-mongodb/src/main/java/io/gravitee/repository/mongodb/management/internal/membership/MembershipMongoRepository.java b/gravitee-apim-repository/gravitee-apim-repository-mongodb/src/main/java/io/gravitee/repository/mongodb/management/internal/membership/MembershipMongoRepository.java index bf9775f5646..9aa3b2a4bb8 100644 --- a/gravitee-apim-repository/gravitee-apim-repository-mongodb/src/main/java/io/gravitee/repository/mongodb/management/internal/membership/MembershipMongoRepository.java +++ b/gravitee-apim-repository/gravitee-apim-repository-mongodb/src/main/java/io/gravitee/repository/mongodb/management/internal/membership/MembershipMongoRepository.java @@ -105,6 +105,9 @@ Set findByMemberIdAndMemberTypeAndReferenceTypeAndReferenceIdAn @Query("{ 'memberId' : ?0, 'memberType' : ?1 }") Set findByMemberIdAndMemberType(String memberId, String memberType); + @Query("{ 'referenceId' : ?0, 'referenceType' : ?1 }") + Set findReferenceIdAndReferenceType(String referenceId, String referenceType); + @Query(value = "{ 'referenceId': ?0, 'referenceType': ?1 }", fields = "{ _id : 1 }", delete = true) List deleteByReferenceIdAndReferenceType(String referenceId, String referenceType); } diff --git a/gravitee-apim-repository/gravitee-apim-repository-test/README.md b/gravitee-apim-repository/gravitee-apim-repository-test/README.md index 7b6a2acb1c6..a991170df84 100644 --- a/gravitee-apim-repository/gravitee-apim-repository-test/README.md +++ b/gravitee-apim-repository/gravitee-apim-repository-test/README.md @@ -8,7 +8,7 @@ This repository is used to ensure repository test coverage and to provide a mini The minimum requirement is : * Maven3 - * Jdk8 + * Jdk21 For user gravitee snapshot, You need the declare the following repository in you maven settings : @@ -19,7 +19,7 @@ https://oss.sonatype.org/content/repositories/snapshots ``` $ git clone https://github.com/gravitee-io/gravitee-repository-test.git -$ cd gravitee-repository-test +$ cd gravitee-apim-repository/gravitee-apim-repository-test $ mvn clean package ``` diff --git a/gravitee-apim-repository/gravitee-apim-repository-test/src/test/java/io/gravitee/repository/management/MembershipRepositoryTest.java b/gravitee-apim-repository/gravitee-apim-repository-test/src/test/java/io/gravitee/repository/management/MembershipRepositoryTest.java index fc6d0c69f6e..6c3fd50feea 100644 --- a/gravitee-apim-repository/gravitee-apim-repository-test/src/test/java/io/gravitee/repository/management/MembershipRepositoryTest.java +++ b/gravitee-apim-repository/gravitee-apim-repository-test/src/test/java/io/gravitee/repository/management/MembershipRepositoryTest.java @@ -16,13 +16,14 @@ package io.gravitee.repository.management; import static io.gravitee.repository.management.model.MembershipReferenceType.API; -import static io.gravitee.repository.utils.DateUtils.compareDate; -import static org.junit.Assert.*; +import static io.gravitee.repository.utils.DateUtils.close; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; import io.gravitee.repository.exceptions.TechnicalException; import io.gravitee.repository.management.model.*; +import java.time.Instant; import java.util.*; -import java.util.stream.Collectors; import org.junit.Test; /** @@ -39,41 +40,42 @@ protected String getTestCasesPath() { @Test public void shouldFindById() throws TechnicalException { Optional membership = membershipRepository.findById("api1_user1"); - assertTrue("There is a membership", membership.isPresent()); - assertTrue(membership.get().getRoleId().equals("API_OWNER")); - assertEquals("api1", membership.get().getReferenceId()); - assertEquals("user1", membership.get().getMemberId()); - assertEquals(API, membership.get().getReferenceType()); - assertEquals("myIdp", membership.get().getSource()); - assertTrue(compareDate(new Date(1439022010883L), membership.get().getUpdatedAt())); - assertTrue(compareDate(new Date(1439022010883L), membership.get().getCreatedAt())); - assertEquals("API_OWNER", membership.get().getRoleId()); + assertThat(membership).isPresent(); + + Membership membership1 = membership.get(); + + assertThat(membership1.getRoleId()).isEqualTo("API_OWNER"); + assertThat(membership1.getReferenceId()).isEqualTo("api1"); + assertThat(membership1.getMemberId()).isEqualTo("user1"); + assertThat(membership1.getReferenceType()).isEqualTo(API); + assertThat(membership1.getSource()).isEqualTo("myIdp"); + + assertThat(membership1.getUpdatedAt()).is(close("2015-08-08T08:20:10.883+00:00")); + assertThat(membership1.getCreatedAt()).is(close("2015-08-08T08:20:10.883+00:00")); } @Test public void shouldNotFindById() throws TechnicalException { Optional membership = membershipRepository.findById("api1"); - assertFalse(membership.isPresent()); + assertThat(membership).isEmpty(); } @Test public void shouldFindAllApiMembers() throws TechnicalException { Set memberships = membershipRepository.findByReferenceAndRoleId(MembershipReferenceType.API, "api1", null); - assertNotNull("result must not be null", memberships); - assertTrue(!memberships.isEmpty()); - assertEquals("user1", memberships.iterator().next().getMemberId()); + assertThat(memberships).map(Membership::getMemberId).contains("user1"); } @Test public void shouldFindAllApisMembers() throws TechnicalException { + // When Set memberships = membershipRepository.findByReferencesAndRoleId( MembershipReferenceType.API, - Arrays.asList("api2", "api3"), + List.of("api2", "api3"), null ); - assertNotNull("result must not be null", memberships); - assertEquals(2, memberships.size()); - Membership membership1 = new Membership( + // Then + var membership1 = new Membership( "api2_user2", "user2", MembershipMemberType.USER, @@ -82,35 +84,26 @@ public void shouldFindAllApisMembers() throws TechnicalException { "API_OWNER" ); membership1.setId("api2_user2"); - Membership membership2 = new Membership( - "api3_user3", - "user3", - MembershipMemberType.USER, - "api3", - MembershipReferenceType.API, - "API_USER" - ); + var membership2 = new Membership("api3_user3", "user3", MembershipMemberType.USER, "api3", MembershipReferenceType.API, "API_USER"); membership2.setId("api3_user3"); - Set expectedResult = new HashSet<>(Arrays.asList(membership1, membership2)); - assertTrue("must contain api2 and api3", memberships.containsAll(expectedResult)); + assertThat(memberships).containsOnly(membership1, membership2); } @Test public void shouldReturnEmptyListWithEmptyReferenceIdList() throws TechnicalException { Set memberships = membershipRepository.findByReferencesAndRoleId(MembershipReferenceType.API, List.of(), null); - assertNotNull("result must not be null", memberships); - assertTrue(memberships.isEmpty()); + assertThat(memberships).isEmpty(); } @Test public void shouldFindApisOwners() throws TechnicalException { + // When Set memberships = membershipRepository.findByReferencesAndRoleId( MembershipReferenceType.API, - Arrays.asList("api2", "api3"), + List.of("api2", "api3"), "API_OWNER" ); - assertNotNull("result must not be null", memberships); - assertEquals(1, memberships.size()); + // Then Membership membership1 = new Membership( "api2_user2", "user2", @@ -120,19 +113,27 @@ public void shouldFindApisOwners() throws TechnicalException { "API_OWNER" ); membership1.setId("api2_user2"); - Set expectedResult = new HashSet<>(Collections.singletonList(membership1)); - assertTrue("must contain api2", memberships.containsAll(expectedResult)); + assertThat(memberships).containsOnly(membership1); + } + + @Test + public void shouldFindByReferenceIdAndReferenceType() throws TechnicalException { + // When + List memberships = membershipRepository.findByReferenceIdAndReferenceType("api1", MembershipReferenceType.API); + // Then + assertThat(memberships) + .containsOnly( + new Membership("api1_user1", "user1", MembershipMemberType.USER, "api1", MembershipReferenceType.API, "API_OWNER") + ); } @Test public void shouldFindMembersApis() throws TechnicalException { Set memberships = membershipRepository.findByMemberIdsAndMemberTypeAndReferenceType( - Arrays.asList("user2", "user3"), + List.of("user2", "user3"), MembershipMemberType.USER, MembershipReferenceType.API ); - assertNotNull("result must not be null", memberships); - assertEquals(2, memberships.size()); Membership membership2 = new Membership( "api2_user2", "user2", @@ -149,152 +150,137 @@ public void shouldFindMembersApis() throws TechnicalException { MembershipReferenceType.API, "API_OWNER" ); - Set expectedResult = new HashSet<>(Arrays.asList(membership2, membership3)); - assertTrue("must contain api2 and api3", memberships.containsAll(expectedResult)); + assertThat(memberships).containsOnly(membership2, membership3); } @Test public void shouldFindApiOwner() throws TechnicalException { - Set memberships = membershipRepository.findByReferenceAndRoleId(MembershipReferenceType.API, "api1", "API_OWNER"); - assertNotNull("result must not be null", memberships); - assertTrue(!memberships.isEmpty()); - assertEquals("user1", memberships.iterator().next().getMemberId()); + var memberships = membershipRepository.findByReferenceAndRoleId(MembershipReferenceType.API, "api1", "API_OWNER"); + assertThat(memberships).map(Membership::getMemberId).contains("user1"); } @Test public void shouldFindByMemberIdAndMemberTypeAndReferenceType() throws TechnicalException { - Set memberships = membershipRepository.findByMemberIdAndMemberTypeAndReferenceType( + var memberships = membershipRepository.findByMemberIdAndMemberTypeAndReferenceType( "user1", MembershipMemberType.USER, MembershipReferenceType.API ); - assertNotNull("result must not be null", memberships); - assertTrue(!memberships.isEmpty()); - assertEquals("api1", memberships.iterator().next().getReferenceId()); + assertThat(memberships).map(Membership::getReferenceId).contains("api1"); } @Test public void shouldfindByMemberIdAndMemberTypeAndReferenceTypeAndSource() throws TechnicalException { - Set memberships = membershipRepository.findByMemberIdAndMemberTypeAndReferenceTypeAndSource( + var memberships = membershipRepository.findByMemberIdAndMemberTypeAndReferenceTypeAndSource( "user1", MembershipMemberType.USER, MembershipReferenceType.API, "myIdp" ); - assertNotNull("result must not be null", memberships); - assertTrue(!memberships.isEmpty()); - assertEquals("api1", memberships.iterator().next().getReferenceId()); + assertThat(memberships).map(Membership::getReferenceId).contains("api1"); } @Test public void shouldFindByMemberIdAndMemberTypeAndReferenceTypeAndRoleId() throws TechnicalException { - Set memberships = membershipRepository.findByMemberIdAndMemberTypeAndReferenceTypeAndRoleId( + var memberships = membershipRepository.findByMemberIdAndMemberTypeAndReferenceTypeAndRoleId( "user1", MembershipMemberType.USER, MembershipReferenceType.API, "API_OWNER" ); - assertNotNull("result must not be null", memberships); - assertTrue(!memberships.isEmpty()); + assertThat(memberships).isNotEmpty(); Membership membership = memberships.iterator().next(); - assertEquals("API_OWNER", membership.getRoleId()); - assertEquals("api1", membership.getReferenceId()); - assertEquals("user1", membership.getMemberId()); + assertThat(membership.getReferenceId()).isEqualTo("api1"); + assertThat(membership.getMemberId()).isEqualTo("user1"); + assertThat(membership.getRoleId()).isEqualTo("API_OWNER"); } @Test public void shouldFindByMemberIdAndMemberTypeAndReferenceTypeAndRoleIdIn() throws TechnicalException { - Set memberships = membershipRepository.findByMemberIdAndMemberTypeAndReferenceTypeAndRoleIdIn( + var memberships = membershipRepository.findByMemberIdAndMemberTypeAndReferenceTypeAndRoleIdIn( "user1", MembershipMemberType.USER, MembershipReferenceType.API, Set.of("API_OWNER", "UNKNOWN_ROLE") ); - assertNotNull("result must not be null", memberships); - assertTrue(!memberships.isEmpty()); + assertThat(memberships).isNotEmpty(); Membership membership = memberships.iterator().next(); - assertEquals("API_OWNER", membership.getRoleId()); - assertEquals("api1", membership.getReferenceId()); - assertEquals("user1", membership.getMemberId()); + assertThat(membership.getReferenceId()).isEqualTo("api1"); + assertThat(membership.getMemberId()).isEqualTo("user1"); + assertThat(membership.getRoleId()).isEqualTo("API_OWNER"); } @Test public void shouldFindRefIdByMemberAndRefTypeAndRoleIdIn() throws TechnicalException { - Set referenceIds = membershipRepository.findRefIdByMemberAndRefTypeAndRoleIdIn( + var referenceIds = membershipRepository.findRefIdByMemberAndRefTypeAndRoleIdIn( "user1", MembershipMemberType.USER, MembershipReferenceType.API, Set.of("API_OWNER", "UNKNOWN_ROLE") ); - assertNotNull("result must not be null", referenceIds); - assertTrue(!referenceIds.isEmpty()); - assertEquals(1, referenceIds.size()); - assertTrue(referenceIds.containsAll(Set.of("api1"))); + assertThat(referenceIds).containsExactly("api1"); } @Test public void shouldFindByMemberIdAndMemberTypeAndReferenceTypeAndReferenceId() throws TechnicalException { - Set memberships = membershipRepository.findByMemberIdAndMemberTypeAndReferenceTypeAndReferenceId( + var memberships = membershipRepository.findByMemberIdAndMemberTypeAndReferenceTypeAndReferenceId( "user1", MembershipMemberType.USER, MembershipReferenceType.API, "api1" ); - assertNotNull("result must not be null", memberships); - assertTrue(!memberships.isEmpty()); + assertThat(memberships).isNotEmpty(); Membership membership = memberships.iterator().next(); - assertEquals("API_OWNER", membership.getRoleId()); - assertEquals("api1", membership.getReferenceId()); - assertEquals("user1", membership.getMemberId()); + assertThat(membership.getReferenceId()).isEqualTo("api1"); + assertThat(membership.getMemberId()).isEqualTo("user1"); + assertThat(membership.getRoleId()).isEqualTo("API_OWNER"); } @Test public void shouldFindByMemberIdAndMemberTypeAndReferenceTypeAndReferenceIdAndRoleId() throws TechnicalException { - Set memberships = membershipRepository.findByMemberIdAndMemberTypeAndReferenceTypeAndReferenceIdAndRoleId( + var memberships = membershipRepository.findByMemberIdAndMemberTypeAndReferenceTypeAndReferenceIdAndRoleId( "user1", MembershipMemberType.USER, MembershipReferenceType.API, "api1", "API_OWNER" ); - assertNotNull("result must not be null", memberships); - assertTrue(!memberships.isEmpty()); + assertThat(memberships).isNotEmpty(); Membership membership = memberships.iterator().next(); - assertEquals("API_OWNER", membership.getRoleId()); - assertEquals("api1", membership.getReferenceId()); - assertEquals("user1", membership.getMemberId()); + assertThat(membership.getReferenceId()).isEqualTo("api1"); + assertThat(membership.getMemberId()).isEqualTo("user1"); + assertThat(membership.getRoleId()).isEqualTo("API_OWNER"); } @Test public void shouldFindMembershipWithNullReferenceId() throws TechnicalException { - Set memberships = membershipRepository.findByMemberIdAndMemberTypeAndReferenceTypeAndReferenceId( + var memberships = membershipRepository.findByMemberIdAndMemberTypeAndReferenceTypeAndReferenceId( "group1", MembershipMemberType.GROUP, MembershipReferenceType.API, null ); - assertNotNull("result must not be null", memberships); - assertFalse(memberships.isEmpty()); + assertThat(memberships).isNotEmpty(); Membership membership = memberships.iterator().next(); - assertEquals("11baec92-8823-4f8b-baec-9288238f8b5c", membership.getRoleId()); - assertNull(membership.getReferenceId()); - assertEquals("group1", membership.getMemberId()); + assertThat(membership.getReferenceId()).isNull(); + assertThat(membership.getMemberId()).isEqualTo("group1"); + assertThat(membership.getRoleId()).isEqualTo("11baec92-8823-4f8b-baec-9288238f8b5c"); } @Test public void shouldFindByRoleId() throws TechnicalException { - Set memberships = membershipRepository.findByRoleId("APPLICATION_USER"); - assertNotNull("result must not be null", memberships); - assertTrue(!memberships.isEmpty()); - assertEquals("size", 2, memberships.size()); + var memberships = membershipRepository.findByRoleId("APPLICATION_USER"); + + assertThat(memberships).hasSize(2); } @Test public void shouldDelete() throws TechnicalException { membershipRepository.delete("app1_userToDelete"); - Optional optional = membershipRepository.findById("app1_userToDelete"); - assertFalse("There is no membership", optional.isPresent()); + var optional = membershipRepository.findById("app1_userToDelete"); + + assertThat(optional).isEmpty(); } @Test @@ -308,63 +294,43 @@ public void shouldUpdate() throws TechnicalException { "APPLICATION_USER" ); membership.setId("app1_userToUpdate"); - membership.setCreatedAt(new Date(1000000000000L)); + membership.setCreatedAt(Date.from(Instant.parse("2001-09-09T01:46:40Z"))); + // When Membership update = membershipRepository.update(membership); - assertNotNull(update); - assertTrue(compareDate(new Date(1000000000000L), update.getCreatedAt())); + assertThat(update).isNotNull(); + assertThat(update.getCreatedAt()).is(close("2001-09-09T01:46:40Z")); } @Test public void shouldFindByIds() throws TechnicalException { - Set memberships = membershipRepository.findByIds( - new HashSet<>(Arrays.asList("api1_user_findByIds", "api2_user_findByIds", "unknown")) - ); + Set memberships = membershipRepository.findByIds(Set.of("api1_user_findByIds", "api2_user_findByIds", "unknown")); - assertNotNull(memberships); - assertFalse(memberships.isEmpty()); - assertEquals(2, memberships.size()); - assertTrue( - memberships - .stream() - .map(Membership::getId) - .collect(Collectors.toList()) - .containsAll(Arrays.asList("api1_user_findByIds", "api2_user_findByIds")) - ); + assertThat(memberships).hasSize(2).map(Membership::getId).contains("api1_user_findByIds", "api2_user_findByIds"); } @Test public void shouldFindByMemberIdAndMemberType() throws TechnicalException { Set memberships = membershipRepository.findByMemberIdAndMemberType("user_findByIds", MembershipMemberType.USER); - assertNotNull(memberships); - assertFalse(memberships.isEmpty()); - assertEquals(2, memberships.size()); - assertTrue( - memberships - .stream() - .map(Membership::getId) - .collect(Collectors.toList()) - .containsAll(Arrays.asList("api1_user_findByIds", "api2_user_findByIds")) - ); + assertThat(memberships).hasSize(2).map(Membership::getId).contains("api1_user_findByIds", "api2_user_findByIds"); } - @Test(expected = IllegalStateException.class) - public void shouldNotUpdateUnknownMembership() throws Exception { + public void shouldNotUpdateUnknownMembership() { Membership unknownMembership = new Membership(); unknownMembership.setId("unknown"); unknownMembership.setMemberId("unknown"); unknownMembership.setReferenceId("unknown"); unknownMembership.setReferenceType(MembershipReferenceType.ENVIRONMENT); - membershipRepository.update(unknownMembership); - fail("An unknown membership should not be updated"); + Throwable throwable = catchThrowable(() -> membershipRepository.update(unknownMembership)); + assertThat(throwable).isInstanceOf(IllegalStateException.class); } - @Test(expected = IllegalStateException.class) public void shouldNotUpdateNull() throws Exception { membershipRepository.update(null); - fail("A null membership should not be updated"); + Throwable throwable = catchThrowable(() -> membershipRepository.update(null)); + assertThat(throwable).isInstanceOf(IllegalStateException.class); } @Test @@ -375,8 +341,8 @@ public void shouldDeleteMembers() throws TechnicalException { int nbAfterDeletion = membershipRepository.findByReferenceAndRoleId(API, "api_deleteRef", null).size(); - assertEquals(2, nbBeforeDeletion); - assertEquals(2, deleted.size()); - assertEquals(0, nbAfterDeletion); + assertThat(nbBeforeDeletion).isEqualTo(2); + assertThat(nbAfterDeletion).isZero(); + assertThat(deleted).hasSize(2); } } diff --git a/gravitee-apim-repository/gravitee-apim-repository-test/src/test/java/io/gravitee/repository/utils/DateUtils.java b/gravitee-apim-repository/gravitee-apim-repository-test/src/test/java/io/gravitee/repository/utils/DateUtils.java index f254db8efb5..7c5e51be9dd 100644 --- a/gravitee-apim-repository/gravitee-apim-repository-test/src/test/java/io/gravitee/repository/utils/DateUtils.java +++ b/gravitee-apim-repository/gravitee-apim-repository-test/src/test/java/io/gravitee/repository/utils/DateUtils.java @@ -17,8 +17,10 @@ import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.Instant; import java.util.Date; import java.util.TimeZone; +import org.assertj.core.api.Condition; public class DateUtils { @@ -54,4 +56,12 @@ public static boolean compareDate(Date expectedDate, Date actualDate) { public static boolean compareDate(long expectedTimestamp, long actualTimestamp) { return Math.abs(expectedTimestamp - actualTimestamp) < 3; } + + public static Condition close(String expected) { + return new Condition<>( + v -> v != null && compareDate(v.getTime(), Instant.parse(expected).toEpochMilli()), + "is close to %s", + expected + ); + } } diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/api/domain_service/ApiExportDomainService.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/api/domain_service/ApiExportDomainService.java index 5f548d10581..b711dcedd5c 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/api/domain_service/ApiExportDomainService.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/api/domain_service/ApiExportDomainService.java @@ -16,9 +16,17 @@ package io.gravitee.apim.core.api.domain_service; import io.gravitee.apim.core.api.model.import_definition.GraviteeDefinition; -import io.gravitee.apim.core.api.model.import_definition.ImportDefinition; import io.gravitee.apim.core.audit.model.AuditInfo; +import java.util.Collection; public interface ApiExportDomainService { - GraviteeDefinition export(String apiId, AuditInfo auditInfo); + enum Excludable { + GROUPS, + PLANS, + MEMBERS, + PAGES_MEDIA, + METADATA, + } + + GraviteeDefinition export(String apiId, AuditInfo auditInfo, Collection excludeAdditionalData); } diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/api/model/import_definition/ApiDescriptor.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/api/model/import_definition/ApiDescriptor.java new file mode 100644 index 00000000000..86020fecda0 --- /dev/null +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/api/model/import_definition/ApiDescriptor.java @@ -0,0 +1,193 @@ +/* + * Copyright © 2015 The Gravitee team (http://gravitee.io) + * + * 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.gravitee.apim.core.api.model.import_definition; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.gravitee.common.component.Lifecycle; +import io.gravitee.definition.model.DefinitionVersion; +import io.gravitee.definition.model.ResponseTemplate; +import io.gravitee.definition.model.v4.ApiType; +import io.gravitee.definition.model.v4.analytics.Analytics; +import io.gravitee.definition.model.v4.endpointgroup.EndpointGroup; +import io.gravitee.definition.model.v4.failover.Failover; +import io.gravitee.definition.model.v4.flow.Flow; +import io.gravitee.definition.model.v4.flow.execution.FlowExecution; +import io.gravitee.definition.model.v4.listener.Listener; +import io.gravitee.definition.model.v4.nativeapi.NativeEndpointGroup; +import io.gravitee.definition.model.v4.nativeapi.NativeFlow; +import io.gravitee.definition.model.v4.nativeapi.NativeListener; +import io.gravitee.definition.model.v4.property.Property; +import io.gravitee.definition.model.v4.resource.Resource; +import io.gravitee.definition.model.v4.service.ApiServices; +import io.gravitee.rest.api.model.PrimaryOwnerEntity; +import io.gravitee.rest.api.model.Visibility; +import io.gravitee.rest.api.model.WorkflowState; +import io.gravitee.rest.api.model.api.ApiLifecycleState; +import io.gravitee.rest.api.model.context.OriginContext; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Set; +import lombok.Builder; + +public sealed interface ApiDescriptor { + String id(); + String crossId(); + String name(); + String apiVersion(); + DefinitionVersion definitionVersion(); + ApiType type(); + String description(); + Instant deployedAt(); + Instant createdAt(); + Instant updatedAt(); + boolean disableMembershipNotifications(); + Map metadata(); + Set groups(); + Lifecycle.State state(); + Visibility visibility(); + List labels(); + ApiLifecycleState lifecycleState(); + Set tags(); + PrimaryOwnerEntity primaryOwner(); + Set categories(); + OriginContext originContext(); + WorkflowState workflowState(); + String picture(); + String background(); + + @Builder + record ApiDescriptorV4( + String id, + String crossId, + String name, + String apiVersion, + ApiType type, + String description, + Instant deployedAt, + Instant createdAt, + Instant updatedAt, + boolean disableMembershipNotifications, + Map metadata, + Set groups, + Lifecycle.State state, + Visibility visibility, + List labels, + ApiLifecycleState lifecycleState, + Set tags, + PrimaryOwnerEntity primaryOwner, + Set categories, + OriginContext originContext, + WorkflowState workflowState, + String picture, + String background, + List listeners, + List endpointGroups, + Analytics analytics, + FlowExecution flowExecution, + List flows, + Map> responseTemplates, + List properties, + List resources, + ApiServices services, + Failover failover + ) + implements ApiDescriptor { + @JsonProperty("definitionVersion") + @Override + public DefinitionVersion definitionVersion() { + return DefinitionVersion.V4; + } + } + + @Builder + record ApiDescriptorNative( + String id, + String crossId, + String name, + String apiVersion, + String description, + Instant deployedAt, + Instant createdAt, + Instant updatedAt, + boolean disableMembershipNotifications, + Map metadata, + Set groups, + Lifecycle.State state, + Visibility visibility, + List labels, + ApiLifecycleState lifecycleState, + Set tags, + PrimaryOwnerEntity primaryOwner, + Set categories, + OriginContext originContext, + WorkflowState workflowState, + String picture, + String background, + List listeners, + List endpointGroups, + List flows, + List properties, + List resources + ) + implements ApiDescriptor { + @JsonProperty("definitionVersion") + @Override + public DefinitionVersion definitionVersion() { + return DefinitionVersion.V4; + } + + @JsonProperty("type") + @Override + public ApiType type() { + return ApiType.NATIVE; + } + } + + @Builder + record ApiDescriptorFederated( + String id, + String crossId, + String name, + String apiVersion, + ApiType type, + String description, + Instant deployedAt, + Instant createdAt, + Instant updatedAt, + boolean disableMembershipNotifications, + Map metadata, + Set groups, + Lifecycle.State state, + Visibility visibility, + List labels, + ApiLifecycleState lifecycleState, + Set tags, + PrimaryOwnerEntity primaryOwner, + Set categories, + OriginContext originContext, + WorkflowState workflowState, + String picture, + String background + ) + implements ApiDescriptor { + @JsonProperty("definitionVersion") + @Override + public DefinitionVersion definitionVersion() { + return DefinitionVersion.FEDERATED; + } + } +} diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/api/model/import_definition/GraviteeDefinition.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/api/model/import_definition/GraviteeDefinition.java index c3c0da449dc..322938632e5 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/api/model/import_definition/GraviteeDefinition.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/api/model/import_definition/GraviteeDefinition.java @@ -16,37 +16,113 @@ package io.gravitee.apim.core.api.model.import_definition; import io.gravitee.apim.core.api.model.NewApiMetadata; -import io.gravitee.apim.core.documentation.model.Page; import io.gravitee.apim.core.media.model.Media; -import io.gravitee.apim.core.plan.model.PlanWithFlows; +import io.gravitee.common.util.Version; +import io.gravitee.common.utils.TimeProvider; +import java.time.Instant; +import java.util.Collection; import java.util.List; import java.util.Set; -import lombok.AllArgsConstructor; import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; /** * Represents the definition of an exported API. */ -@AllArgsConstructor -@NoArgsConstructor -@Data -@Builder(toBuilder = true) -public class GraviteeDefinition { +public sealed interface GraviteeDefinition { + Export export(); + ApiDescriptor api(); + Set members(); + Set metadata(); + List pages(); + Collection plans(); + List apiMedia(); + String apiPicture(); + String apiBackground(); - private ApiExport api; + @Builder(toBuilder = true) + record V4( + Export export, + ApiDescriptor.ApiDescriptorV4 api, + Set members, + Set metadata, + List pages, + Collection plans, + List apiMedia, + String apiPicture, + String apiBackground + ) + implements GraviteeDefinition { + public V4( + ApiDescriptor.ApiDescriptorV4 api, + Set members, + Set metadata, + List pages, + Collection plans, + List apiMedia, + String apiPicture, + String apiBackground + ) { + this(new Export(), api, members, metadata, pages, plans, apiMedia, apiPicture, apiBackground); + } + } - private Set members; + @Builder(toBuilder = true) + record Native( + Export export, + ApiDescriptor.ApiDescriptorNative api, + Set members, + Set metadata, + List pages, + Collection plans, + List apiMedia, + String apiPicture, + String apiBackground + ) + implements GraviteeDefinition { + public Native( + ApiDescriptor.ApiDescriptorNative api, + Set members, + Set metadata, + List pages, + Collection plans, + List apiMedia, + String apiPicture, + String apiBackground + ) { + this(new Export(), api, members, metadata, pages, plans, apiMedia, apiPicture, apiBackground); + } + } - private Set metadata; + @Builder(toBuilder = true) + record GraviteeDefinitionFederated( + Export export, + ApiDescriptor.ApiDescriptorFederated api, + Set members, + Set metadata, + List pages, + Collection plans, + List apiMedia, + String apiPicture, + String apiBackground + ) + implements GraviteeDefinition { + public GraviteeDefinitionFederated( + ApiDescriptor.ApiDescriptorFederated api, + Set members, + Set metadata, + List pages, + Collection plans, + List apiMedia, + String apiPicture, + String apiBackground + ) { + this(new Export(), api, members, metadata, pages, plans, apiMedia, apiPicture, apiBackground); + } + } - private List pages; - - private Set plans; - - private List apiMedia; - - private String apiPicture; - private String apiBackground; + record Export(Instant date, String exportVersion, String apimVersion) { + public Export() { + this(TimeProvider.instantNow(), "1", Version.RUNTIME_VERSION.MAJOR_VERSION); + } + } } diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/api/model/import_definition/PlanDescriptor.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/api/model/import_definition/PlanDescriptor.java new file mode 100644 index 00000000000..87d9a4222f4 --- /dev/null +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/api/model/import_definition/PlanDescriptor.java @@ -0,0 +1,83 @@ +/* + * Copyright © 2015 The Gravitee team (http://gravitee.io) + * + * 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.gravitee.apim.core.api.model.import_definition; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.gravitee.apim.core.plan.model.Plan; +import io.gravitee.definition.model.DefinitionVersion; +import io.gravitee.definition.model.v4.ApiType; +import io.gravitee.definition.model.v4.flow.AbstractFlow; +import io.gravitee.definition.model.v4.plan.PlanMode; +import io.gravitee.definition.model.v4.plan.PlanSecurity; +import io.gravitee.definition.model.v4.plan.PlanStatus; +import java.time.Instant; +import java.util.List; +import java.util.Set; +import lombok.Builder; + +public sealed interface PlanDescriptor { + String id(); + String name(); + + PlanStatus status(); + + @JsonIgnore + default boolean closed() { + return PlanStatus.CLOSED.equals(status()); + } + + @Builder + record PlanDescriptorV4( + String id, + String crossId, + String name, + DefinitionVersion definitionVersion, + String description, + + Instant createdAt, + Instant updatedAt, + Instant publishedAt, + Instant closedAt, + + Plan.PlanValidationType validation, + Plan.PlanType type, + PlanMode mode, + PlanSecurity security, + + Set tags, + + String selectionRule, + PlanStatus status, + String apiId, + String environmentId, + + int order, + List characteristics, + List excludedGroups, + boolean commentRequired, + String commentMessage, + String generalConditions, + + List flows + ) + implements PlanDescriptor { + @JsonProperty("tags") + public Set tags() { + return tags != null ? tags : Set.of(); + } + } +} diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/membership/crud_service/MembershipCrudService.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/membership/crud_service/MembershipCrudService.java index 014d44b3efa..d8786c48f53 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/membership/crud_service/MembershipCrudService.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/membership/crud_service/MembershipCrudService.java @@ -16,8 +16,10 @@ package io.gravitee.apim.core.membership.crud_service; import io.gravitee.apim.core.membership.model.Membership; +import java.util.Collection; public interface MembershipCrudService { Membership create(Membership membership); void delete(String id); + Collection findByApiId(String apiId); } diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/metadata/crud_service/MetadataCrudService.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/metadata/crud_service/MetadataCrudService.java index c288081f170..05f2034e84f 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/metadata/crud_service/MetadataCrudService.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/metadata/crud_service/MetadataCrudService.java @@ -17,11 +17,13 @@ import io.gravitee.apim.core.metadata.model.Metadata; import io.gravitee.apim.core.metadata.model.MetadataId; +import java.util.Collection; import java.util.Optional; public interface MetadataCrudService { Metadata create(Metadata metadata); Optional findById(MetadataId id); + Collection findByApiId(String id); Metadata update(Metadata metadata); void delete(MetadataId metadataId); } diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/plan/crud_service/PlanCrudService.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/plan/crud_service/PlanCrudService.java index c569f0f0ee9..033b3387a41 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/plan/crud_service/PlanCrudService.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/plan/crud_service/PlanCrudService.java @@ -16,6 +16,7 @@ package io.gravitee.apim.core.plan.crud_service; import io.gravitee.apim.core.plan.model.Plan; +import java.util.Collection; import java.util.Optional; public interface PlanCrudService { @@ -23,6 +24,8 @@ public interface PlanCrudService { Optional findById(String planId); + Collection findByApiId(String apiId); + Plan create(Plan plan); Plan update(Plan plan); diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/scoring/use_case/ScoreApiRequestUseCase.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/scoring/use_case/ScoreApiRequestUseCase.java index d2e3343422c..65db273839d 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/scoring/use_case/ScoreApiRequestUseCase.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/scoring/use_case/ScoreApiRequestUseCase.java @@ -20,6 +20,7 @@ import io.gravitee.apim.core.api.crud_service.ApiCrudService; import io.gravitee.apim.core.api.domain_service.ApiExportDomainService; import io.gravitee.apim.core.api.exception.ApiNotFoundException; +import io.gravitee.apim.core.api.model.import_definition.ApiDescriptor; import io.gravitee.apim.core.api.model.import_definition.GraviteeDefinition; import io.gravitee.apim.core.async_job.crud_service.AsyncJobCrudService; import io.gravitee.apim.core.async_job.model.AsyncJob; @@ -37,12 +38,13 @@ import io.gravitee.apim.core.scoring.service_provider.ScoringProvider; import io.gravitee.apim.core.utils.StringUtils; import io.gravitee.common.utils.TimeProvider; -import io.gravitee.definition.model.DefinitionVersion; import io.gravitee.rest.api.service.common.UuidString; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Maybe; import io.reactivex.rxjava3.core.Single; +import jakarta.annotation.Nullable; +import java.util.EnumSet; import java.util.List; import lombok.RequiredArgsConstructor; @@ -80,7 +82,9 @@ public Completable execute(Input input) { .toList(); var export$ = Flowable - .fromCallable(() -> apiExportDomainService.export(input.apiId, input.auditInfo)) + .fromCallable(() -> + apiExportDomainService.export(input.apiId, input.auditInfo, EnumSet.noneOf(ApiExportDomainService.Excludable.class)) + ) .map(this::assetToScore) // export service throw error in some case (like if API isn't V4) .onErrorResumeNext(th -> Flowable.empty()); @@ -120,39 +124,35 @@ private ScoreRequest.AssetToScore assetToScore(Page page) { } private ScoreRequest.AssetToScore assetToScore(GraviteeDefinition definition) throws JsonProcessingException { - if (definition.getApi().getEndpointGroups() != null) { - // remove shared configuration because this produce some errors in scoring (invalid reference: APIM-7877) - definition.getApi().getEndpointGroups().forEach(endpoint -> endpoint.setSharedConfiguration((String) null)); - } return new ScoreRequest.AssetToScore( - definition.getApi().getId(), - new ScoreRequest.AssetType( - ScoringAssetType.GRAVITEE_DEFINITION, - definition.getApi().getDefinitionVersion() == DefinitionVersion.FEDERATED - ? ScoreRequest.Format.GRAVITEE_FEDERATED - : switch (definition.getApi().getType()) { - case PROXY -> ScoreRequest.Format.GRAVITEE_PROXY; - case MESSAGE -> ScoreRequest.Format.GRAVITEE_MESSAGE; - default -> null; - } - ), - definition.getApi().getName(), + definition.api().id(), + new ScoreRequest.AssetType(ScoringAssetType.GRAVITEE_DEFINITION, getFormat(definition)), + definition.api().name(), graviteeDefinitionSerializer.serialize(definition) ); } - private Maybe customRuleset(ScoringRuleset scoringRuleset) { - if (StringUtils.isEmpty(scoringRuleset.payload())) { - return Maybe.empty(); + @Nullable + private static ScoreRequest.Format getFormat(GraviteeDefinition definition) { + if (definition.api() instanceof ApiDescriptor.ApiDescriptorFederated) { + return ScoreRequest.Format.GRAVITEE_FEDERATED; } - return Maybe.just(new ScoreRequest.CustomRuleset(scoringRuleset.payload(), format(scoringRuleset.format()))); + return switch (definition.api().type()) { + case PROXY -> ScoreRequest.Format.GRAVITEE_PROXY; + case MESSAGE -> ScoreRequest.Format.GRAVITEE_MESSAGE; + default -> null; + }; + } + + private Maybe customRuleset(ScoringRuleset scoringRuleset) { + return StringUtils.isEmpty(scoringRuleset.payload()) + ? Maybe.empty() + : Maybe.just(new ScoreRequest.CustomRuleset(scoringRuleset.payload(), format(scoringRuleset.format()))); } private ScoreRequest.Format format(ScoringRuleset.Format format) { - if (format == null) { - return null; - } return switch (format) { + case null -> null; case GRAVITEE_FEDERATION -> ScoreRequest.Format.GRAVITEE_FEDERATED; case GRAVITEE_MESSAGE -> ScoreRequest.Format.GRAVITEE_MESSAGE; case GRAVITEE_PROXY -> ScoreRequest.Format.GRAVITEE_PROXY; diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/workflow/crud_service/WorkflowCrudService.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/workflow/crud_service/WorkflowCrudService.java index 94f838176f1..f2dd9512db2 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/workflow/crud_service/WorkflowCrudService.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/workflow/crud_service/WorkflowCrudService.java @@ -16,7 +16,9 @@ package io.gravitee.apim.core.workflow.crud_service; import io.gravitee.apim.core.workflow.model.Workflow; +import java.util.Collection; public interface WorkflowCrudService { Workflow create(Workflow workflow); + Collection findByApiId(String apiId); } diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/adapter/GraviteeDefinitionAdapter.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/adapter/GraviteeDefinitionAdapter.java index b9169709f75..d60fefd71ec 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/adapter/GraviteeDefinitionAdapter.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/adapter/GraviteeDefinitionAdapter.java @@ -15,11 +15,32 @@ */ package io.gravitee.apim.infra.adapter; -import io.gravitee.apim.core.api.model.import_definition.GraviteeDefinition; -import io.gravitee.apim.core.api.model.import_definition.PlanExport; -import io.gravitee.rest.api.model.v4.api.ExportApiEntity; -import io.gravitee.rest.api.model.v4.plan.BasePlanEntity; -import io.gravitee.rest.api.model.v4.plan.GenericPlanEntity; +import static io.gravitee.apim.core.utils.CollectionUtils.stream; + +import io.gravitee.apim.core.api.model.Api; +import io.gravitee.apim.core.api.model.NewApiMetadata; +import io.gravitee.apim.core.api.model.import_definition.ApiDescriptor; +import io.gravitee.apim.core.api.model.import_definition.PageExport; +import io.gravitee.apim.core.api.model.import_definition.PlanDescriptor; +import io.gravitee.apim.core.documentation.model.Page; +import io.gravitee.apim.core.media.model.Media; +import io.gravitee.apim.core.membership.model.PrimaryOwnerEntity; +import io.gravitee.apim.core.metadata.model.Metadata; +import io.gravitee.apim.core.plan.model.Plan; +import io.gravitee.definition.model.v4.endpointgroup.EndpointGroup; +import io.gravitee.definition.model.v4.nativeapi.NativeEndpointGroup; +import io.gravitee.definition.model.v4.plan.PlanSecurity; +import io.gravitee.rest.api.model.MediaEntity; +import io.gravitee.rest.api.model.WorkflowState; +import io.gravitee.rest.api.model.v4.plan.PlanSecurityType; +import jakarta.annotation.Nullable; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; @@ -31,9 +52,95 @@ public interface GraviteeDefinitionAdapter { GraviteeDefinitionAdapter INSTANCE = Mappers.getMapper(GraviteeDefinitionAdapter.class); Logger logger = LoggerFactory.getLogger(GraviteeDefinitionAdapter.class); - @Mapping(target = "api", source = "apiEntity") - GraviteeDefinition map(ExportApiEntity source); + List mapPage(Collection source); + + List mapMedia(Collection src); + + @Mapping(target = "security", expression = "java(mapPlanSecurity(source.getPlanDefinitionHttpV4().getSecurity()))") + @Mapping(target = "mode", source = "planDefinitionHttpV4.mode") + @Mapping(target = "status", source = "planDefinitionHttpV4.status") + PlanDescriptor.PlanDescriptorV4 mapPlanV4(Plan source); + + @Mapping(target = "security", expression = "java(mapPlanSecurity(source.getPlanDefinitionNativeV4().getSecurity()))") + @Mapping(target = "mode", source = "planDefinitionNativeV4.mode") + @Mapping(target = "status", source = "planDefinitionNativeV4.status") + PlanDescriptor.PlanDescriptorV4 mapPlanNative(Plan source); + + @Nullable + default Collection mapPlanV4(Collection src) { + return src == null + ? null + : src.stream().map(plan -> plan.getPlanDefinitionV4() != null ? mapPlanV4(plan) : mapPlanNative(plan)).toList(); + } + + io.gravitee.rest.api.model.PrimaryOwnerEntity map(PrimaryOwnerEntity src); + + @Mapping(target = "id", source = "apiEntity.id") + @Mapping(target = "type", source = "apiEntity.type") + @Mapping(target = "state", source = "apiEntity.lifecycleState") + @Mapping(target = "lifecycleState", source = "apiEntity.apiLifecycleState") + @Mapping(target = "listeners", source = "apiEntity.apiDefinitionHttpV4.listeners") + @Mapping(target = "analytics", source = "apiEntity.apiDefinitionHttpV4.analytics") + @Mapping(target = "flowExecution", source = "apiEntity.apiDefinitionHttpV4.flowExecution") + @Mapping(target = "flows", source = "apiEntity.apiDefinitionHttpV4.flows") + @Mapping(target = "responseTemplates", source = "apiEntity.apiDefinitionHttpV4.responseTemplates") + @Mapping(target = "properties", source = "apiEntity.apiDefinitionHttpV4.properties") + @Mapping(target = "resources", source = "apiEntity.apiDefinitionHttpV4.resources") + @Mapping( + target = "failover", + expression = "java(apiEntity.getApiDefinitionHttpV4() != null ? apiEntity.getApiDefinitionHttpV4().getFailover() : null)" + ) + @Mapping(target = "endpointGroups", source = "apiEntity.apiDefinitionHttpV4.endpointGroups") + ApiDescriptor.ApiDescriptorV4 mapV4( + Api apiEntity, + PrimaryOwnerEntity primaryOwner, + WorkflowState workflowState, + Set groups, + Collection metadata + ); + + @Mapping(target = "id", source = "apiEntity.id") + @Mapping(target = "state", source = "apiEntity.lifecycleState") + @Mapping(target = "lifecycleState", source = "apiEntity.apiLifecycleState") + @Mapping(target = "listeners", source = "apiEntity.apiDefinitionNativeV4.listeners") + @Mapping(target = "flows", source = "apiEntity.apiDefinitionNativeV4.flows") + @Mapping(target = "properties", source = "apiEntity.apiDefinitionNativeV4.properties") + @Mapping(target = "resources", source = "apiEntity.apiDefinitionNativeV4.resources") + @Mapping(target = "endpointGroups", source = "apiEntity.apiDefinitionNativeV4.endpointGroups") + ApiDescriptor.ApiDescriptorNative mapNative( + Api apiEntity, + PrimaryOwnerEntity primaryOwner, + WorkflowState workflowState, + Set groups, + Collection metadata + ); + + Set mapMetadata(Collection source); + + default Map map(Collection sources) { + return stream(sources).collect(Collectors.toMap(NewApiMetadata::getName, NewApiMetadata::getValue)); + } + + default Instant map(ZonedDateTime src) { + return src == null ? null : src.toInstant(); + } - @Mapping(target = "type", source = "planType") - PlanExport genericPlanMap(GenericPlanEntity source); + /** + * Map a PlanSecurity. + *

+ * This is required to ensure the PlanSecurityType as the same form as the one in the export. + *

+ * @param source the source PlanSecurity + * @return the mapped PlanSecurity + */ + default PlanSecurity mapPlanSecurity(io.gravitee.definition.model.v4.plan.PlanSecurity source) { + if (source == null) { + return null; + } + return PlanSecurity + .builder() + .type(PlanSecurityType.valueOfLabel(source.getType()).name()) + .configuration(source.getConfiguration()) + .build(); + } } diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/adapter/MemberAdapter.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/adapter/MemberAdapter.java index 9e4d6f66dcb..8aea99ebc56 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/adapter/MemberAdapter.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/adapter/MemberAdapter.java @@ -18,6 +18,7 @@ import io.gravitee.apim.core.api.model.import_definition.ApiMember; import io.gravitee.apim.core.api.model.import_definition.ApiMemberRole; import io.gravitee.apim.core.member.model.Member; +import io.gravitee.apim.core.membership.model.Membership; import io.gravitee.rest.api.model.MemberEntity; import io.gravitee.rest.api.model.RoleEntity; import java.util.List; @@ -29,7 +30,7 @@ public interface MemberAdapter { MemberAdapter INSTANCE = Mappers.getMapper(MemberAdapter.class); - ApiMember toApiMember(MemberEntity member); + ApiMember toApiMember(Membership member); MemberEntity toEntity(ApiMember member); Set toEntities(Set members); diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/documentation/PageCrudServiceImpl.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/documentation/PageCrudServiceImpl.java index f6ec13771fa..3708939069a 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/documentation/PageCrudServiceImpl.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/documentation/PageCrudServiceImpl.java @@ -15,6 +15,8 @@ */ package io.gravitee.apim.infra.crud_service.documentation; +import static io.gravitee.apim.core.utils.CollectionUtils.stream; + import io.gravitee.apim.core.documentation.crud_service.PageCrudService; import io.gravitee.apim.core.documentation.exception.ApiPageInvalidReferenceTypeException; import io.gravitee.apim.core.documentation.exception.ApiPageNotDeletedException; @@ -23,8 +25,11 @@ import io.gravitee.apim.infra.adapter.PageAdapter; import io.gravitee.repository.exceptions.TechnicalException; import io.gravitee.repository.management.api.PageRepository; +import io.gravitee.repository.management.api.search.PageCriteria; +import io.gravitee.repository.management.model.PageReferenceType; import io.gravitee.rest.api.service.exceptions.PageNotFoundException; import java.util.Collection; +import java.util.List; import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/membership/MembershipCrudServiceImpl.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/membership/MembershipCrudServiceImpl.java index 3d90eef1ffd..9c09dc00dbb 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/membership/MembershipCrudServiceImpl.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/membership/MembershipCrudServiceImpl.java @@ -15,12 +15,16 @@ */ package io.gravitee.apim.infra.crud_service.membership; +import static io.gravitee.apim.core.utils.CollectionUtils.stream; + import io.gravitee.apim.core.exception.TechnicalDomainException; import io.gravitee.apim.core.membership.crud_service.MembershipCrudService; import io.gravitee.apim.core.membership.model.Membership; import io.gravitee.apim.infra.adapter.MembershipAdapter; import io.gravitee.repository.exceptions.TechnicalException; import io.gravitee.repository.management.api.MembershipRepository; +import io.gravitee.repository.management.model.MembershipReferenceType; +import java.util.Collection; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -51,4 +55,15 @@ public void delete(String id) { throw new TechnicalDomainException("An error occurs while trying to delete the membership: " + id, e); } } + + @Override + public Collection findByApiId(String apiId) { + try { + return stream(membershipRepository.findByReferenceIdAndReferenceType(apiId, MembershipReferenceType.API)) + .map(MembershipAdapter.INSTANCE::toEntity) + .toList(); + } catch (TechnicalException e) { + throw new TechnicalDomainException("An error occurs while trying to create the membership: " + apiId, e); + } + } } diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/metadata/MetadataCrudServiceImpl.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/metadata/MetadataCrudServiceImpl.java index 552dc923257..4e93add5916 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/metadata/MetadataCrudServiceImpl.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/metadata/MetadataCrudServiceImpl.java @@ -15,6 +15,9 @@ */ package io.gravitee.apim.infra.crud_service.metadata; +import static io.gravitee.apim.core.utils.CollectionUtils.stream; +import static io.gravitee.repository.management.model.MetadataReferenceType.API; + import io.gravitee.apim.core.exception.TechnicalDomainException; import io.gravitee.apim.core.metadata.crud_service.MetadataCrudService; import io.gravitee.apim.core.metadata.model.Metadata; @@ -23,6 +26,7 @@ import io.gravitee.repository.exceptions.TechnicalException; import io.gravitee.repository.management.api.MetadataRepository; import io.gravitee.repository.management.model.MetadataReferenceType; +import java.util.Collection; import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; @@ -74,6 +78,15 @@ public Optional findById(MetadataId id) { } } + @Override + public Collection findByApiId(String id) { + try { + return stream(metadataRepository.findByReferenceTypeAndReferenceId(API, id)).map(MetadataAdapter.INSTANCE::toEntity).toList(); + } catch (TechnicalException e) { + throw new TechnicalDomainException("An error occurred while finding metadata by API id [%s]".formatted(id), e); + } + } + @Override public Metadata update(Metadata metadata) { try { diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/plan/PlanCrudServiceImpl.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/plan/PlanCrudServiceImpl.java index 8e0799c89bb..362e460ce61 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/plan/PlanCrudServiceImpl.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/plan/PlanCrudServiceImpl.java @@ -15,6 +15,8 @@ */ package io.gravitee.apim.infra.crud_service.plan; +import static io.gravitee.apim.core.utils.CollectionUtils.stream; + import io.gravitee.apim.core.exception.TechnicalDomainException; import io.gravitee.apim.core.plan.crud_service.PlanCrudService; import io.gravitee.apim.core.plan.model.Plan; @@ -22,6 +24,7 @@ import io.gravitee.repository.exceptions.TechnicalException; import io.gravitee.repository.management.api.PlanRepository; import io.gravitee.rest.api.service.exceptions.PlanNotFoundException; +import java.util.Collection; import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; @@ -60,6 +63,16 @@ public Optional findById(String planId) { } } + @Override + public Collection findByApiId(String apiId) { + try { + log.debug("Find a plan by API id : {}", apiId); + return stream(planRepository.findByApi(apiId)).map(PlanAdapter.INSTANCE::fromRepository).toList(); + } catch (TechnicalException ex) { + throw new TechnicalDomainException(String.format("An error occurs while trying to find a plan by id: %s", apiId), ex); + } + } + @Override public Plan create(Plan plan) { try { diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/workflow/WorkflowCrudServiceImpl.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/workflow/WorkflowCrudServiceImpl.java index 75e046ea036..4c10a41027f 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/workflow/WorkflowCrudServiceImpl.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/crud_service/workflow/WorkflowCrudServiceImpl.java @@ -15,12 +15,17 @@ */ package io.gravitee.apim.infra.crud_service.workflow; +import static io.gravitee.apim.core.utils.CollectionUtils.stream; + import io.gravitee.apim.core.exception.TechnicalDomainException; import io.gravitee.apim.core.workflow.crud_service.WorkflowCrudService; import io.gravitee.apim.core.workflow.model.Workflow; import io.gravitee.apim.infra.adapter.WorkflowAdapter; import io.gravitee.repository.exceptions.TechnicalException; import io.gravitee.repository.management.api.WorkflowRepository; +import io.gravitee.rest.api.model.WorkflowReferenceType; +import io.gravitee.rest.api.model.WorkflowType; +import java.util.Collection; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -49,4 +54,14 @@ public Workflow create(Workflow workflow) { ); } } + + public Collection findByApiId(String apiId) { + try { + return stream(workflowRepository.findByReferenceAndType(WorkflowReferenceType.API.name(), apiId, WorkflowType.REVIEW.name())) + .map(WorkflowAdapter.INSTANCE::toEntity) + .toList(); + } catch (TechnicalException e) { + throw new TechnicalDomainException("An error to find workflows of [apiId=%s]".formatted(apiId), e); + } + } } diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/domain_service/api/ApiExportDomainServiceImpl.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/domain_service/api/ApiExportDomainServiceImpl.java index 9306746286f..339fa4b60bc 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/domain_service/api/ApiExportDomainServiceImpl.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/infra/domain_service/api/ApiExportDomainServiceImpl.java @@ -15,15 +15,52 @@ */ package io.gravitee.apim.infra.domain_service.api; +import static io.gravitee.apim.core.api.domain_service.ApiExportDomainService.Excludable.GROUPS; +import static io.gravitee.apim.core.api.domain_service.ApiExportDomainService.Excludable.MEMBERS; +import static io.gravitee.apim.core.api.domain_service.ApiExportDomainService.Excludable.METADATA; +import static io.gravitee.apim.core.api.domain_service.ApiExportDomainService.Excludable.PAGES_MEDIA; +import static io.gravitee.apim.core.utils.CollectionUtils.stream; +import static io.gravitee.rest.api.model.permissions.RolePermission.API_DOCUMENTATION; +import static io.gravitee.rest.api.model.permissions.RolePermission.API_MEMBER; +import static io.gravitee.rest.api.model.permissions.RolePermission.API_PLAN; +import static io.gravitee.rest.api.model.permissions.RolePermissionAction.READ; + +import io.gravitee.apim.core.api.crud_service.ApiCrudService; import io.gravitee.apim.core.api.domain_service.ApiExportDomainService; +import io.gravitee.apim.core.api.exception.ApiNotFoundException; +import io.gravitee.apim.core.api.model.Api; +import io.gravitee.apim.core.api.model.NewApiMetadata; +import io.gravitee.apim.core.api.model.import_definition.ApiDescriptor; +import io.gravitee.apim.core.api.model.import_definition.ApiMember; import io.gravitee.apim.core.api.model.import_definition.GraviteeDefinition; +import io.gravitee.apim.core.api.model.import_definition.PageExport; +import io.gravitee.apim.core.api.model.import_definition.PlanDescriptor; import io.gravitee.apim.core.audit.model.AuditInfo; +import io.gravitee.apim.core.documentation.query_service.PageQueryService; +import io.gravitee.apim.core.media.model.Media; +import io.gravitee.apim.core.membership.crud_service.MembershipCrudService; +import io.gravitee.apim.core.membership.domain_service.ApiPrimaryOwnerDomainService; +import io.gravitee.apim.core.membership.model.Membership; +import io.gravitee.apim.core.membership.model.PrimaryOwnerEntity; +import io.gravitee.apim.core.metadata.crud_service.MetadataCrudService; +import io.gravitee.apim.core.plan.crud_service.PlanCrudService; +import io.gravitee.apim.core.workflow.crud_service.WorkflowCrudService; +import io.gravitee.apim.core.workflow.model.Workflow; import io.gravitee.apim.infra.adapter.GraviteeDefinitionAdapter; -import io.gravitee.rest.api.model.v4.api.ApiEntity; -import io.gravitee.rest.api.model.v4.plan.BasePlanEntity; +import io.gravitee.apim.infra.adapter.MemberAdapter; +import io.gravitee.definition.model.DefinitionVersion; +import io.gravitee.rest.api.model.WorkflowState; +import io.gravitee.rest.api.model.permissions.RolePermission; +import io.gravitee.rest.api.service.MediaService; +import io.gravitee.rest.api.service.PermissionService; import io.gravitee.rest.api.service.common.ExecutionContext; -import io.gravitee.rest.api.service.v4.ApiImportExportService; +import io.gravitee.rest.api.service.common.GraviteeContext; +import io.gravitee.rest.api.service.exceptions.ApiDefinitionVersionNotSupportedException; +import jakarta.annotation.Nullable; +import java.util.Collection; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -31,28 +68,110 @@ @RequiredArgsConstructor public class ApiExportDomainServiceImpl implements ApiExportDomainService { - private final ApiImportExportService exportService; + private final PermissionService permissionService; + private final MediaService mediaService; + + private final WorkflowCrudService workflowCrudService; + private final MembershipCrudService membershipCrudService; + private final MetadataCrudService metadataCrudService; + private final PageQueryService pageQueryService; + private final ApiCrudService apiCrudService; + private final ApiPrimaryOwnerDomainService apiPrimaryOwnerDomainService; + private final PlanCrudService planCrudService; @Override - public GraviteeDefinition export(String apiId, AuditInfo auditInfo) { + public GraviteeDefinition export(String apiId, AuditInfo auditInfo, Collection excluded) { var executionContext = new ExecutionContext(auditInfo.organizationId(), auditInfo.environmentId()); - var exportEntity = exportService.exportApi(executionContext, apiId, null, Set.of()); - var graviteeDefinition = GraviteeDefinitionAdapter.INSTANCE.map(exportEntity); - if (exportEntity.getApiEntity() instanceof ApiEntity v4) { - graviteeDefinition.getApi().setType(v4.getType()); - } - if (exportEntity.getPlans() != null) { - for (var source : exportEntity.getPlans()) { - if (source instanceof BasePlanEntity v4Plan) { - graviteeDefinition - .getPlans() - .stream() - .filter(p -> p.getId().equals(v4Plan.getId())) - .findFirst() - .ifPresent(target -> target.setSecurity(v4Plan.getSecurity())); - } - } + + var apiPrimaryOwner = apiPrimaryOwnerDomainService.getApiPrimaryOwner(auditInfo.organizationId(), apiId); + var api1 = apiCrudService.findById(apiId).orElseThrow(() -> new ApiNotFoundException(apiId)); + + var members = !excluded.contains(MEMBERS) ? exportApiMembers(apiId) : null; + var metadata = !excluded.contains(METADATA) ? exportApiMetadata(executionContext, apiId) : null; + + var pages = !excluded.contains(PAGES_MEDIA) ? exportApiPages(apiId) : null; + var medias = !excluded.contains(PAGES_MEDIA) ? exportApiMedia(apiId) : null; + + var workflowState = stream(workflowCrudService.findByApiId(apiId)) + .map(Workflow::getState) + .map(Enum::name) + .map(WorkflowState::valueOf) + .findFirst() + .orElse(null); + + if (api1.getDefinitionVersion() == DefinitionVersion.V4 && api1.getApiDefinitionHttpV4() != null) { + var api = exportV4(api1, apiPrimaryOwner, metadata, workflowState, excluded); + var plans = !excluded.contains(Excludable.PLANS) ? exportApiPlansV4(apiId) : null; + return new GraviteeDefinition.V4(api, members, metadata, pages, plans, medias, api1.getPicture(), api1.getBackground()); + } else if (api1.getDefinitionVersion() == DefinitionVersion.V4 && api1.getApiDefinitionNativeV4() != null) { + var api = exportNative(api1, apiPrimaryOwner, metadata, workflowState, excluded); + var plans = !excluded.contains(Excludable.PLANS) ? exportApiPlansNative(apiId) : null; + return new GraviteeDefinition.Native(api, members, metadata, pages, plans, medias, api1.getPicture(), api1.getBackground()); + } else { + throw new ApiDefinitionVersionNotSupportedException( + api1.getDefinitionVersion() != null ? api1.getDefinitionVersion().getLabel() : null + ); } - return graviteeDefinition; + } + + private ApiDescriptor.ApiDescriptorV4 exportV4( + Api apiEntity, + PrimaryOwnerEntity primaryOwner, + @Nullable Collection metadata, + @Nullable WorkflowState workflowState, + Collection excluded + ) { + var groups = excluded.contains(GROUPS) ? null : apiEntity.getGroups(); + return GraviteeDefinitionAdapter.INSTANCE.mapV4(apiEntity, primaryOwner, workflowState, groups, metadata); + } + + private ApiDescriptor.ApiDescriptorNative exportNative( + Api nativeApi, + PrimaryOwnerEntity primaryOwner, + @Nullable Collection metadata, + @Nullable WorkflowState workflowState, + Collection excluded + ) { + var groups = excluded.contains(GROUPS) ? null : nativeApi.getGroups(); + return GraviteeDefinitionAdapter.INSTANCE.mapNative(nativeApi, primaryOwner, workflowState, groups, metadata); + } + + private Set exportApiMembers(String apiId) { + return permissionService.hasPermission(GraviteeContext.getExecutionContext(), API_MEMBER, apiId, READ) + ? stream(membershipCrudService.findByApiId(apiId)) + .filter(memberEntity -> memberEntity.getMemberType() == Membership.Type.USER) + .map(MemberAdapter.INSTANCE::toApiMember) + .collect(Collectors.toSet()) + : null; + } + + private Set exportApiMetadata(ExecutionContext executionContext, String apiId) { + return permissionService.hasPermission(executionContext, RolePermission.API_METADATA, apiId, READ) + ? GraviteeDefinitionAdapter.INSTANCE.mapMetadata(metadataCrudService.findByApiId(apiId)) + : null; + } + + private List exportApiPages(String apiId) { + return permissionService.hasPermission(GraviteeContext.getExecutionContext(), API_DOCUMENTATION, apiId, READ) + ? GraviteeDefinitionAdapter.INSTANCE.mapPage(pageQueryService.searchByApiId(apiId)) + : null; + } + + private List exportApiMedia(String apiId) { + return permissionService.hasPermission(GraviteeContext.getExecutionContext(), API_DOCUMENTATION, apiId, READ) + ? GraviteeDefinitionAdapter.INSTANCE.mapMedia(mediaService.findAllByApiId(apiId)) + : null; + } + + private Collection exportApiPlansV4(String apiId) { + return permissionService.hasPermission(GraviteeContext.getExecutionContext(), API_PLAN, apiId, READ) + ? GraviteeDefinitionAdapter.INSTANCE.mapPlanV4(planCrudService.findByApiId(apiId)) + : null; + } + + private Collection exportApiPlansNative(String apiId) { + return permissionService.hasPermission(GraviteeContext.getExecutionContext(), API_PLAN, apiId, READ) + ? GraviteeDefinitionAdapter.INSTANCE.mapPlanV4(planCrudService.findByApiId(apiId)) + : null; } } diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/fixtures/core/model/ApiFixtures.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/fixtures/core/model/ApiFixtures.java index 718acd9c888..e76b2ae6c45 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/fixtures/core/model/ApiFixtures.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/fixtures/core/model/ApiFixtures.java @@ -50,11 +50,12 @@ public class ApiFixtures { private ApiFixtures() {} public static final String MY_API = "my-api"; + public static final String MY_API_NAME = "My Api"; private static final Supplier BASE = () -> Api .builder() .id(MY_API) - .name("My Api") + .name(MY_API_NAME) .environmentId("environment-id") .crossId("my-api-crossId") .description("api-description") @@ -82,7 +83,7 @@ public static Api aProxyApiV4() { io.gravitee.definition.model.v4.Api .builder() .id(MY_API) - .name("My Api") + .name(MY_API_NAME) .apiVersion("1.0.0") .analytics(Analytics.builder().enabled(false).build()) .failover(Failover.builder().enabled(false).build()) diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/fixtures/core/model/GraviteeDefinitionFixtures.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/fixtures/core/model/GraviteeDefinitionFixtures.java index 8ee88479cd7..b9cb622a54f 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/fixtures/core/model/GraviteeDefinitionFixtures.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/fixtures/core/model/GraviteeDefinitionFixtures.java @@ -16,12 +16,12 @@ package fixtures.core.model; import io.gravitee.apim.core.api.model.NewApiMetadata; -import io.gravitee.apim.core.api.model.import_definition.ApiExport; +import io.gravitee.apim.core.api.model.import_definition.ApiDescriptor; import io.gravitee.apim.core.api.model.import_definition.ApiMember; import io.gravitee.apim.core.api.model.import_definition.ApiMemberRole; import io.gravitee.apim.core.api.model.import_definition.GraviteeDefinition; import io.gravitee.apim.core.api.model.import_definition.PageExport; -import io.gravitee.apim.core.api.model.import_definition.PlanExport; +import io.gravitee.apim.core.api.model.import_definition.PlanDescriptor; import io.gravitee.apim.core.documentation.model.Page; import io.gravitee.apim.core.metadata.model.Metadata; import io.gravitee.apim.core.plan.model.Plan; @@ -67,8 +67,8 @@ public class GraviteeDefinitionFixtures { private GraviteeDefinitionFixtures() {} - public static final Supplier BASE = () -> - GraviteeDefinition + public static final Supplier BASE = () -> + GraviteeDefinition.V4 .builder() .members( Set.of( @@ -95,13 +95,12 @@ private GraviteeDefinitionFixtures() {} .apiPicture("data:image/png;base64,picture") .apiBackground("data:image/png;base64,background"); - public static GraviteeDefinition aGraviteeDefinitionProxy() { + public static GraviteeDefinition.V4 aGraviteeDefinitionProxy() { return BASE .get() .api( - ApiExport + ApiDescriptor.ApiDescriptorV4 .builder() - .definitionVersion(DefinitionVersion.V4) .type(ApiType.PROXY) .listeners( List.of( @@ -250,7 +249,7 @@ public static GraviteeDefinition aGraviteeDefinitionProxy() { ) .plans( Set.of( - PlanExport + PlanDescriptor.PlanDescriptorV4 .builder() .id("plan-id") .name("Default Keyless (UNSECURED)") diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/inmemory/MembershipCrudServiceInMemory.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/inmemory/MembershipCrudServiceInMemory.java index 6f3268723f0..89d1bce2ffd 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/inmemory/MembershipCrudServiceInMemory.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/inmemory/MembershipCrudServiceInMemory.java @@ -18,6 +18,7 @@ import io.gravitee.apim.core.membership.crud_service.MembershipCrudService; import io.gravitee.apim.core.membership.model.Membership; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; @@ -36,6 +37,14 @@ public void delete(String id) { storage.removeIf(s -> s.getId().equals(id)); } + @Override + public Collection findByApiId(String apiId) { + return storage + .stream() + .filter(m -> m.getReferenceType() == Membership.ReferenceType.API && apiId.equals(m.getReferenceId())) + .toList(); + } + @Override public void initWith(List items) { storage.addAll(items); diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/inmemory/MetadataCrudServiceInMemory.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/inmemory/MetadataCrudServiceInMemory.java index d825209b6e2..084377ccf3e 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/inmemory/MetadataCrudServiceInMemory.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/inmemory/MetadataCrudServiceInMemory.java @@ -19,6 +19,7 @@ import io.gravitee.apim.core.metadata.model.Metadata; import io.gravitee.apim.core.metadata.model.MetadataId; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -47,6 +48,11 @@ public Optional findById(MetadataId id) { .findFirst(); } + @Override + public Collection findByApiId(String id) { + return storage.stream().filter(m -> Metadata.ReferenceType.API == m.getReferenceType() && id.equals(m.getReferenceId())).toList(); + } + @Override public Metadata update(Metadata metadata) { OptionalInt index = diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/inmemory/PlanCrudServiceInMemory.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/inmemory/PlanCrudServiceInMemory.java index 9de179a3325..a8a65a9c17d 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/inmemory/PlanCrudServiceInMemory.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/inmemory/PlanCrudServiceInMemory.java @@ -20,6 +20,7 @@ import io.gravitee.rest.api.service.exceptions.PlanNotFoundException; import io.gravitee.rest.api.service.exceptions.TechnicalManagementException; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -46,6 +47,11 @@ public Optional findById(String planId) { return storage.stream().filter(plan -> planId.equals(plan.getId())).findFirst(); } + @Override + public Collection findByApiId(String apiId) { + return storage.stream().filter(plan -> plan.getApiId().equals(apiId)).toList(); + } + @Override public Plan create(Plan plan) { storage.add(plan); diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/inmemory/WorkflowCrudServiceInMemory.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/inmemory/WorkflowCrudServiceInMemory.java index 7011c9adf34..1b83d7a9cfa 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/inmemory/WorkflowCrudServiceInMemory.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/inmemory/WorkflowCrudServiceInMemory.java @@ -18,6 +18,7 @@ import io.gravitee.apim.core.workflow.crud_service.WorkflowCrudService; import io.gravitee.apim.core.workflow.model.Workflow; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import org.springframework.stereotype.Service; @@ -33,6 +34,14 @@ public Workflow create(Workflow entity) { return entity; } + @Override + public Collection findByApiId(String apiId) { + return storage + .stream() + .filter(w -> w.getReferenceType() == Workflow.ReferenceType.API && apiId.equals(w.getReferenceId())) + .toList(); + } + @Override public void initWith(List items) { storage.addAll(items); diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/apim/core/scoring/use_case/ScoreApiRequestUseCaseTest.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/apim/core/scoring/use_case/ScoreApiRequestUseCaseTest.java index 9b0615f990a..c43dbd94018 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/apim/core/scoring/use_case/ScoreApiRequestUseCaseTest.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/apim/core/scoring/use_case/ScoreApiRequestUseCaseTest.java @@ -17,6 +17,9 @@ import static assertions.CoreAssertions.assertThat; import static fixtures.core.model.ApiFixtures.MY_API; +import static fixtures.core.model.ApiFixtures.MY_API_NAME; +import static org.mockito.ArgumentMatchers.anyCollection; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -34,7 +37,7 @@ import inmemory.ScoringRulesetQueryServiceInMemory; import io.gravitee.apim.core.api.domain_service.ApiExportDomainService; import io.gravitee.apim.core.api.model.Api; -import io.gravitee.apim.core.api.model.import_definition.ApiExport; +import io.gravitee.apim.core.api.model.import_definition.ApiDescriptor; import io.gravitee.apim.core.api.model.import_definition.GraviteeDefinition; import io.gravitee.apim.core.async_job.model.AsyncJob; import io.gravitee.apim.core.audit.model.AuditInfo; @@ -45,15 +48,17 @@ import io.gravitee.apim.core.scoring.model.ScoringRuleset; import io.gravitee.apim.infra.json.jackson.GraviteeDefinitionJacksonJsonSerializer; import io.gravitee.common.utils.TimeProvider; -import io.gravitee.definition.model.DefinitionVersion; import io.gravitee.rest.api.service.common.UuidString; import io.gravitee.rest.api.service.exceptions.ApiDefinitionVersionNotSupportedException; +import io.vertx.core.json.JsonObject; import java.time.Clock; import java.time.Instant; import java.time.ZoneId; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; +import org.assertj.core.api.Condition; +import org.assertj.core.data.Index; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -88,6 +93,7 @@ class ScoreApiRequestUseCaseTest { ApiExportDomainService apiExportDomainService = mock(ApiExportDomainService.class); ScoreApiRequestUseCase scoreApiRequestUseCase; + private Condition REQUEST_API_CONDITION; @BeforeAll static void beforeAll() { @@ -115,13 +121,12 @@ void setUp() { scoringFunctionQueryService ); - when(apiExportDomainService.export("my-api", AUDIT_INFO)) - .thenReturn( - GraviteeDefinition - .builder() - .api(ApiExport.builder().id(MY_API).name("My Api").definitionVersion(DefinitionVersion.FEDERATED).build()) - .build() - ); + GraviteeDefinition build = GraviteeDefinition.GraviteeDefinitionFederated + .builder() + .api(ApiDescriptor.ApiDescriptorFederated.builder().id(MY_API).name(MY_API_NAME).build()) + .build(); + when(apiExportDomainService.export(eq("my-api"), eq(AUDIT_INFO), anyCollection())).thenReturn(build); + REQUEST_API_CONDITION = is(MY_API, MY_API_NAME); } @AfterEach @@ -146,23 +151,29 @@ public void should_trigger_scoring_for_gravitee_definition_v4() { // Then assertThat(scoringProvider.pendingRequests()) - .containsExactly( - new ScoreRequest( - "generated-id", - ORGANIZATION_ID, - ENVIRONMENT_ID, + .hasSize(1) + .first() + .usingRecursiveComparison() + .ignoringFields("assets") + .isEqualTo(new ScoreRequest("generated-id", ORGANIZATION_ID, ENVIRONMENT_ID, api.getId(), null)); + assertThat(scoringProvider.pendingRequests().get(0).assets()) + .first() + .usingRecursiveComparison() + .ignoringFields("content") + .isEqualTo( + new ScoreRequest.AssetToScore( api.getId(), - List.of( - new ScoreRequest.AssetToScore( - api.getId(), - new ScoreRequest.AssetType(ScoringAssetType.GRAVITEE_DEFINITION, ScoreRequest.Format.GRAVITEE_FEDERATED), - api.getName(), - """ - {"api":{"id":"my-api","name":"My Api","definitionVersion":"FEDERATED","tags":[],"properties":[],"resources":[],"responseTemplates":{},"state":"STOPPED","originContext":{"origin":"MANAGEMENT"},"disableMembershipNotifications":false}}""" - ) - ) + new ScoreRequest.AssetType(ScoringAssetType.GRAVITEE_DEFINITION, ScoreRequest.Format.GRAVITEE_FEDERATED), + api.getName(), + """ + {"api":{"id":"my-api","name":"My Api","definitionVersion":"FEDERATED","tags":[],"properties":[],"resources":[],"responseTemplates":{},"state":"STOPPED","originContext":{"origin":"MANAGEMENT"},"disableMembershipNotifications":false}}""" ) ); + assertThat(scoringProvider.pendingRequests().get(0).assets()) + .map(ScoreRequest.AssetToScore::content) + .map(JsonObject::new) + .first() + .is(REQUEST_API_CONDITION); assertThat(asyncJobCrudService.storage()) .containsExactly( AsyncJob @@ -184,7 +195,8 @@ public void should_trigger_scoring_for_gravitee_definition_v4() { public void should_trigger_scoring_for_unsupported_version_of_gravitee_definition() { // Given var api = givenExistingApi(ApiFixtures.aFederatedApi()); - when(apiExportDomainService.export("my-api", AUDIT_INFO)).thenThrow(new ApiDefinitionVersionNotSupportedException("UNKNOW")); + when(apiExportDomainService.export(eq("my-api"), eq(AUDIT_INFO), anyCollection())) + .thenThrow(new ApiDefinitionVersionNotSupportedException("UNKNOW")); // When scoreApiRequestUseCase @@ -369,19 +381,40 @@ public void should_ignore_page_type_supported(Page.Type pageType) { .hasJobId("generated-id") .hasOrganizationId(ORGANIZATION_ID) .hasEnvironmentId(ENVIRONMENT_ID) - .hasApiId(api.getId()) - .hasOnlyAssets( + .hasApiId(api.getId()); + assertThat(request.assets()) + .hasSize(1) + .first() + .usingRecursiveComparison() + .ignoringFields("content") + .isEqualTo( new ScoreRequest.AssetToScore( api.getId(), new ScoreRequest.AssetType(ScoringAssetType.GRAVITEE_DEFINITION, ScoreRequest.Format.GRAVITEE_FEDERATED), api.getName(), - """ - {"api":{"id":"my-api","name":"My Api","definitionVersion":"FEDERATED","tags":[],"properties":[],"resources":[],"responseTemplates":{},"state":"STOPPED","originContext":{"origin":"MANAGEMENT"},"disableMembershipNotifications":false}}""" + null ) ); + assertThat(request.assets()) + .map(ScoreRequest.AssetToScore::content) + .map(JsonObject::new) + .hasSize(1) + .has(REQUEST_API_CONDITION, Index.atIndex(0)); }); } + private static Condition is(String id, String name) { + return new Condition<>( + json -> { + JsonObject api = json.getJsonObject("api"); + return name.equals(api.getString("name")) && id.equals(api.getString("id")); + }, + "should have name %s and id %s", + name, + id + ); + } + private Api givenExistingApi(Api api) { apiCrudService.initWith(List.of(api)); return api; diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/apim/infra/domain_service/api/ApiExportDomainServiceImplTest.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/apim/infra/domain_service/api/ApiExportDomainServiceImplTest.java index 97c0d55ed9e..b42e8ecbb4c 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/apim/infra/domain_service/api/ApiExportDomainServiceImplTest.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/apim/infra/domain_service/api/ApiExportDomainServiceImplTest.java @@ -17,23 +17,36 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; +import io.gravitee.apim.core.api.crud_service.ApiCrudService; +import io.gravitee.apim.core.api.domain_service.ApiExportDomainService; +import io.gravitee.apim.core.api.model.Api; import io.gravitee.apim.core.api.model.import_definition.GraviteeDefinition; -import io.gravitee.apim.core.api.model.import_definition.PlanExport; import io.gravitee.apim.core.audit.model.AuditInfo; -import io.gravitee.apim.core.plan.model.Plan; +import io.gravitee.apim.core.documentation.query_service.PageQueryService; +import io.gravitee.apim.core.membership.crud_service.MembershipCrudService; +import io.gravitee.apim.core.membership.domain_service.ApiPrimaryOwnerDomainService; +import io.gravitee.apim.core.metadata.crud_service.MetadataCrudService; +import io.gravitee.apim.core.plan.crud_service.PlanCrudService; +import io.gravitee.apim.core.workflow.crud_service.WorkflowCrudService; +import io.gravitee.definition.model.DefinitionVersion; import io.gravitee.definition.model.v4.ApiType; -import io.gravitee.definition.model.v4.plan.PlanSecurity; -import io.gravitee.rest.api.model.v4.api.ApiEntity; -import io.gravitee.rest.api.model.v4.api.ExportApiEntity; -import io.gravitee.rest.api.model.v4.plan.BasePlanEntity; -import io.gravitee.rest.api.model.v4.plan.PlanType; -import io.gravitee.rest.api.service.v4.ApiImportExportService; -import java.util.Set; +import io.gravitee.definition.model.v4.nativeapi.NativeApi; +import io.gravitee.rest.api.model.permissions.RolePermission; +import io.gravitee.rest.api.model.permissions.RolePermissionAction; +import io.gravitee.rest.api.service.MediaService; +import io.gravitee.rest.api.service.PermissionService; +import io.gravitee.rest.api.service.common.ExecutionContext; +import java.util.EnumSet; +import java.util.Optional; import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentMatchers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -42,30 +55,118 @@ class ApiExportDomainServiceImplTest { @Mock - ApiImportExportService exportService; + PermissionService permissionService; + + @Mock + MediaService mediaService; + + @Mock + WorkflowCrudService workflowCrudService; + + @Mock + MembershipCrudService membershipCrudService; + + @Mock + MetadataCrudService metadataCrudService; + + @Mock + PageQueryService pageQueryService; + + @Mock + ApiCrudService apiCrudService; + + @Mock + ApiPrimaryOwnerDomainService apiPrimaryOwnerDomainService; + + @Mock + PlanCrudService planCrudService; @InjectMocks ApiExportDomainServiceImpl sut; + @BeforeEach + void setUp() { + lenient() + .when( + permissionService.hasPermission( + any(ExecutionContext.class), + any(RolePermission.class), + anyString(), + ArgumentMatchers.any() + ) + ) + .thenReturn(true); + } + @Test void exportServiceMustMapTypeWhenExportV4() { // Given String apiId = UUID.randomUUID().toString(); - ApiEntity api = new ApiEntity(); - api.setType(ApiType.PROXY); - BasePlanEntity plan = new BasePlanEntity(); - plan.setId(UUID.randomUUID().toString()); - plan.setSecurity(new PlanSecurity()); - plan.setType(PlanType.API); - when(exportService.exportApi(any(), any(), any(), any())) - .thenReturn(new ExportApiEntity(api, null, null, null, Set.of(plan), null)); + var definition = new io.gravitee.definition.model.v4.Api(); + Api api = Api + .builder() + .id(apiId) + .type(ApiType.PROXY) + .definitionVersion(DefinitionVersion.V4) + .apiDefinitionHttpV4(definition) + .build(); + when(apiCrudService.findById(anyString())).thenReturn(Optional.of(api)); + + // When + GraviteeDefinition export = sut.export(apiId, AuditInfo.builder().build(), EnumSet.noneOf(ApiExportDomainService.Excludable.class)); + + // Then + assertThat(export.api().type()).isEqualTo(ApiType.PROXY); + } + + @Test + void exportServiceMustMapTypeWhenExportV4WithoutPermissions() { + // Given + String apiId = UUID.randomUUID().toString(); + var definition = new io.gravitee.definition.model.v4.Api(); + Api api = Api + .builder() + .id(apiId) + .type(ApiType.PROXY) + .definitionVersion(DefinitionVersion.V4) + .apiDefinitionHttpV4(definition) + .build(); + when(apiCrudService.findById(anyString())).thenReturn(Optional.of(api)); + when( + permissionService.hasPermission( + any(ExecutionContext.class), + any(RolePermission.class), + anyString(), + ArgumentMatchers.any() + ) + ) + .thenReturn(false); + + // When + GraviteeDefinition export = sut.export(apiId, AuditInfo.builder().build(), EnumSet.noneOf(ApiExportDomainService.Excludable.class)); + + // Then + assertThat(export.api().type()).isEqualTo(ApiType.PROXY); + } + + @Test + void exportServiceMustMapTypeWhenExportV4Native() { + // Given + String apiId = UUID.randomUUID().toString(); + var definition = new NativeApi(); + Api api = Api + .builder() + .id(apiId) + .type(ApiType.NATIVE) + .definitionVersion(DefinitionVersion.V4) + .apiDefinitionNativeV4(definition) + .build(); + when(apiCrudService.findById(anyString())).thenReturn(Optional.of(api)); // When - GraviteeDefinition export = sut.export(apiId, AuditInfo.builder().build()); + GraviteeDefinition export = sut.export(apiId, AuditInfo.builder().build(), EnumSet.noneOf(ApiExportDomainService.Excludable.class)); // Then - assertThat(export.getApi().getType()).isEqualTo(ApiType.PROXY); - assertThat(export.getPlans()).map(PlanExport::getType).first().isEqualTo(Plan.PlanType.API); - assertThat(export.getPlans()).map(PlanExport::getSecurity).first().isNotNull(); + assertThat(export.api().type()).isEqualTo(ApiType.NATIVE); } }