Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/750 policy store api date filter #798

Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ _**For better traceability add the corresponding GitHub issue number in each cha

### Added

- Added filtering by "createdOn", "validUntil" to paging endpoint for Policy Store API: `GET /irs/policies/paged`. #750
- Added autocomplete endpoint Policy Store API: `GET /irs/policies/attributes/{attribute}`. #750
- Added get and delete functionality for contract definitions eclipse-tractusx/traceability-foss#1190

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/********************************************************************************
* Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
* Copyright (c) 2021,2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://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.
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/
package org.eclipse.tractusx.irs.policystore.common;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;

/**
* Date utilities.
*/
public final class DateUtils {

private DateUtils() {
// private constructor (utility class)
}

public static boolean isDateBefore(final OffsetDateTime dateTime, final String referenceDateString) {
return dateTime.isBefore(toOffsetDateTimeAtStartOfDay(referenceDateString));
}

public static boolean isDateAfter(final OffsetDateTime dateTime, final String referenceDateString) {
return dateTime.isAfter(toOffsetDateTimeAtEndOfDay(referenceDateString));
}

public static OffsetDateTime toOffsetDateTimeAtStartOfDay(final String dateString) {
return LocalDate.parse(dateString).atStartOfDay().atOffset(ZoneOffset.UTC);
}

public static OffsetDateTime toOffsetDateTimeAtEndOfDay(final String dateString) {
return LocalDate.parse(dateString).atTime(LocalTime.MAX).atOffset(ZoneOffset.UTC);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,14 @@
import static org.eclipse.tractusx.irs.policystore.common.CommonConstants.PROPERTY_CREATED_ON;
import static org.eclipse.tractusx.irs.policystore.common.CommonConstants.PROPERTY_POLICY_ID;
import static org.eclipse.tractusx.irs.policystore.common.CommonConstants.PROPERTY_VALID_UNTIL;
import static org.eclipse.tractusx.irs.policystore.common.DateUtils.isDateAfter;
import static org.eclipse.tractusx.irs.policystore.common.DateUtils.isDateBefore;
import static org.eclipse.tractusx.irs.policystore.models.SearchCriteria.Operation.AFTER_LOCAL_DATE;
import static org.eclipse.tractusx.irs.policystore.models.SearchCriteria.Operation.BEFORE_LOCAL_DATE;
import static org.eclipse.tractusx.irs.policystore.models.SearchCriteria.Operation.EQUALS;
import static org.eclipse.tractusx.irs.policystore.models.SearchCriteria.Operation.STARTS_WITH;

import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.List;
Expand All @@ -53,7 +58,9 @@
*/
@Service
@Slf4j
@SuppressWarnings({ "PMD.TooManyStaticImports" })
@SuppressWarnings({ "PMD.TooManyStaticImports",
"PMD.ExcessiveImports"
})
public class PolicyPagingService {

/**
Expand Down Expand Up @@ -222,6 +229,7 @@ public Sort.Direction getSortDirection(final Pageable pageable, final String fie
*/
private static class PolicyFilterBuilder {

public static final String MSG_PROPERTY_ONLY_SUPPORTS_THE_FOLLOWING_OPERATIONS = "The property '%s' only supports the following operations: %s";
private final List<SearchCriteria<?>> searchCriteriaList;

/* package */ PolicyFilterBuilder(final List<SearchCriteria<?>> searchCriteriaList) {
Expand All @@ -239,24 +247,18 @@ private static class PolicyFilterBuilder {
}

private Predicate<PolicyWithBpn> getPolicyPredicate(final SearchCriteria<?> searchCriteria) {

if (PROPERTY_BPN.equalsIgnoreCase(searchCriteria.getProperty())) {
return getBpnFilter(searchCriteria);
} else if (PROPERTY_POLICY_ID.equalsIgnoreCase(searchCriteria.getProperty())) {
return getPolicyIdFilter(searchCriteria);
} else if (PROPERTY_ACTION.equalsIgnoreCase(searchCriteria.getProperty())) {
return getActionFilter(searchCriteria);
} else if (PROPERTY_CREATED_ON.equalsIgnoreCase(searchCriteria.getProperty())) {
return getCreatedOnFilter(searchCriteria);
} else if (PROPERTY_VALID_UNTIL.equalsIgnoreCase(searchCriteria.getProperty())) {
return getValidUntilFilter(searchCriteria);
} else {
final String notYetImplementedMessage = "Filtering by '%s' has not been implemented yet";
if (PROPERTY_CREATED_ON.equalsIgnoreCase(searchCriteria.getProperty())) {
// TODO (mfischer): #750: implement createdOn filter incl. test
throw new IllegalArgumentException(notYetImplementedMessage.formatted(PROPERTY_CREATED_ON));
} else if (PROPERTY_VALID_UNTIL.equalsIgnoreCase(searchCriteria.getProperty())) {
// TODO (mfischer): #750: implement validUntil filter incl. test
throw new IllegalArgumentException(notYetImplementedMessage.formatted(PROPERTY_VALID_UNTIL));
} else {
throw new IllegalArgumentException("Not supported");
}
throw new IllegalArgumentException("Not supported");
}
}

Expand All @@ -266,7 +268,7 @@ private Predicate<PolicyWithBpn> getPolicyIdFilter(final SearchCriteria<?> searc
case STARTS_WITH -> p -> StringUtils.startsWithIgnoreCase(p.policy().getPolicyId(),
(String) searchCriteria.getValue());
default -> throw new IllegalArgumentException(
"The property 'policyId' only supports the following operations: %s".formatted(
MSG_PROPERTY_ONLY_SUPPORTS_THE_FOLLOWING_OPERATIONS.formatted(searchCriteria.getProperty(),
List.of(EQUALS, STARTS_WITH)));
};
}
Expand All @@ -276,7 +278,7 @@ private Predicate<PolicyWithBpn> getBpnFilter(final SearchCriteria<?> searchCrit
case EQUALS -> p -> p.bpn().equalsIgnoreCase((String) searchCriteria.getValue());
case STARTS_WITH -> p -> StringUtils.startsWithIgnoreCase(p.bpn(), (String) searchCriteria.getValue());
default -> throw new IllegalArgumentException(
"The property 'BPN' only supports the following operations: %s".formatted(
MSG_PROPERTY_ONLY_SUPPORTS_THE_FOLLOWING_OPERATIONS.formatted(searchCriteria.getProperty(),
List.of(EQUALS, STARTS_WITH)));
};
}
Expand All @@ -297,9 +299,42 @@ private Predicate<PolicyWithBpn> getActionFilter(final SearchCriteria<?> searchC
};
} else {
throw new IllegalArgumentException(
"The property 'action' only supports the following operations: %s".formatted(List.of(EQUALS)));
MSG_PROPERTY_ONLY_SUPPORTS_THE_FOLLOWING_OPERATIONS.formatted(searchCriteria.getProperty(),
List.of(EQUALS)));
}
}

private Predicate<PolicyWithBpn> getCreatedOnFilter(final SearchCriteria<?> searchCriteria) {
return switch (searchCriteria.getOperation()) {
case BEFORE_LOCAL_DATE -> p -> {
final OffsetDateTime createdOn = p.policy().getCreatedOn();
return isDateBefore(createdOn, searchCriteria.getValue().toString());
};
case AFTER_LOCAL_DATE -> p -> {
final OffsetDateTime createdOn = p.policy().getCreatedOn();
return isDateAfter(createdOn, searchCriteria.getValue().toString());
};
default -> throw new IllegalArgumentException(
MSG_PROPERTY_ONLY_SUPPORTS_THE_FOLLOWING_OPERATIONS.formatted(searchCriteria.getProperty(),
List.of(BEFORE_LOCAL_DATE, AFTER_LOCAL_DATE)));
};
}

private Predicate<PolicyWithBpn> getValidUntilFilter(final SearchCriteria<?> searchCriteria) {
return switch (searchCriteria.getOperation()) {
case BEFORE_LOCAL_DATE -> p -> {
final OffsetDateTime createdOn = p.policy().getValidUntil();
return isDateBefore(createdOn, searchCriteria.getValue().toString());
};
case AFTER_LOCAL_DATE -> p -> {
final OffsetDateTime createdOn = p.policy().getValidUntil();
return isDateAfter(createdOn, searchCriteria.getValue().toString());
};
default -> throw new IllegalArgumentException(
MSG_PROPERTY_ONLY_SUPPORTS_THE_FOLLOWING_OPERATIONS.formatted(searchCriteria.getProperty(),
List.of(BEFORE_LOCAL_DATE, AFTER_LOCAL_DATE)));
};
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/********************************************************************************
* Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
* Copyright (c) 2021,2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://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.
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/
package org.eclipse.tractusx.irs.policystore.common;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.stream.Stream;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

class DateUtilsTest {

@ParameterizedTest
@MethodSource("provideDatesForIsDateBefore")
void testIsDateBefore(final OffsetDateTime dateTime, final String referenceDateString, final boolean expected) {
assertThat(DateUtils.isDateBefore(dateTime, referenceDateString)).isEqualTo(expected);
}

static Stream<Arguments> provideDatesForIsDateBefore() {
final OffsetDateTime referenceDateTime = LocalDate.parse("2024-07-05").atStartOfDay().atOffset(ZoneOffset.UTC);
return Stream.of( //
Arguments.of(referenceDateTime, "2024-07-04", false),
Arguments.of(referenceDateTime, "2024-07-05", false),
Arguments.of(referenceDateTime, "2024-07-06", true));
}

@ParameterizedTest
@MethodSource("provideDatesForIsDateAfter")
void testIsDateAfter(final OffsetDateTime dateTime, final String dateString, final boolean expected) {
assertThat(DateUtils.isDateAfter(dateTime, dateString)).isEqualTo(expected);
}

static Stream<Arguments> provideDatesForIsDateAfter() {
final OffsetDateTime referenceDateTime = LocalDate.parse("2023-07-05")
.atTime(LocalTime.MAX)
.atOffset(ZoneOffset.UTC);

return Stream.of( //
Arguments.of(referenceDateTime, "2023-07-04", true),
Arguments.of(referenceDateTime, "2023-07-05", false),
Arguments.of(referenceDateTime, "2023-07-06", false));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.eclipse.tractusx.irs.policystore.common.CommonConstants.PROPERTY_ACTION;
import static org.eclipse.tractusx.irs.policystore.common.CommonConstants.PROPERTY_BPN;
import static org.eclipse.tractusx.irs.policystore.common.CommonConstants.PROPERTY_CREATED_ON;
import static org.eclipse.tractusx.irs.policystore.common.CommonConstants.PROPERTY_POLICY_ID;
import static org.eclipse.tractusx.irs.policystore.common.CommonConstants.PROPERTY_VALID_UNTIL;
import static org.eclipse.tractusx.irs.policystore.models.SearchCriteria.Operation.AFTER_LOCAL_DATE;
import static org.eclipse.tractusx.irs.policystore.models.SearchCriteria.Operation.BEFORE_LOCAL_DATE;
import static org.eclipse.tractusx.irs.policystore.models.SearchCriteria.Operation.EQUALS;
import static org.eclipse.tractusx.irs.policystore.models.SearchCriteria.Operation.STARTS_WITH;
Expand All @@ -40,6 +43,7 @@
import org.eclipse.tractusx.irs.edc.client.policy.Permission;
import org.eclipse.tractusx.irs.edc.client.policy.Policy;
import org.eclipse.tractusx.irs.edc.client.policy.PolicyType;
import org.eclipse.tractusx.irs.policystore.common.DateUtils;
import org.eclipse.tractusx.irs.policystore.models.PolicyWithBpn;
import org.eclipse.tractusx.irs.policystore.models.SearchCriteria;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -142,6 +146,43 @@ public void whenSortedByBpnDescAndPolicyIdAsc() {
// BPN1
"policy-1", "policy-4");
}

@Nested
class SortByDateTests {

final Map<String, List<Policy>> policiesMap = Map.of( //
"BPN2", Arrays.asList( //
createPolicy("policy-3", "2025-04-12", "2029-11-08"), //
createPolicy("policy-5", "2024-09-01", "2030-03-25"), //
createPolicy("policy-2", "2024-09-01", "2027-02-15") //
), "BPN1", Arrays.asList( //
createPolicy("policy-4", "2022-12-31", "2026-08-05"), //
createPolicy("policy-1", "2023-03-15", "2028-07-10") //
));

@Test
public void whenSortedByCreatedOnAscAndValidUntilAsc() {

final Page<PolicyWithBpn> result = testee.getPolicies(policiesMap, PageRequest.of(0, 10,
Sort.by(PROPERTY_CREATED_ON).ascending().and(Sort.by(PROPERTY_VALID_UNTIL).ascending())),
NO_SEARCH_CRITERIA);

assertThat(result.getContent().stream().map(p -> p.policy().getPolicyId()).toList()).containsExactly(
"policy-4", "policy-1", "policy-2", "policy-5", "policy-3");
}

@Test
public void whenSortedByCreatedOnAscAndValidUntilDesc() {

final Page<PolicyWithBpn> result = testee.getPolicies(policiesMap, PageRequest.of(0, 10,
Sort.by(PROPERTY_CREATED_ON).ascending().and(Sort.by(PROPERTY_VALID_UNTIL).descending())),
NO_SEARCH_CRITERIA);

assertThat(result.getContent().stream().map(p -> p.policy().getPolicyId()).toList()).containsExactly(
"policy-4", "policy-1", "policy-5", "policy-2", "policy-3");
}
}

}

@Nested
Expand Down Expand Up @@ -297,6 +338,32 @@ public void filterByMultiple_shouldNarrowDown() {
assertThat(policies).containsExactlyInAnyOrder("[bpn=BPN1,policyId=policy-2]");
}

@Nested
class FilterByDateTests {

final Map<String, List<Policy>> policiesMap = Map.of( //
"BPN2", Arrays.asList( //
createPolicy("policy-3", "2025-04-12", "2029-11-08"), //
createPolicy("policy-5", "2024-09-01", "2030-03-25"), //
createPolicy("policy-2", "2024-09-01", "2027-02-15") //
), "BPN1", Arrays.asList( //
createPolicy("policy-4", "2022-12-31", "2026-08-05"), //
createPolicy("policy-1", "2023-03-15", "2028-07-10") //
));

@Test
public void whenFilteredByCreatedOnAndValidUntil() {

final Page<PolicyWithBpn> result = testee.getPolicies(policiesMap, PageRequest.of(0, 10,
Sort.by(PROPERTY_CREATED_ON).ascending().and(Sort.by(PROPERTY_VALID_UNTIL).ascending())),
List.of(new SearchCriteria<>(PROPERTY_CREATED_ON, BEFORE_LOCAL_DATE, "2024-09-01"),
new SearchCriteria<>(PROPERTY_VALID_UNTIL, AFTER_LOCAL_DATE, "2026-08-04")));

assertThat(result.getContent().stream().map(p -> p.policy().getPolicyId()).toList()).containsExactly(
"policy-4", "policy-1");
}

}
}

private Policy createPolicy(final String policyId, final PolicyType firstPermissionAction) {
Expand All @@ -308,11 +375,25 @@ private Policy createPolicy(final String policyId, final PolicyType firstPermiss
.build();
}

private Policy createPolicy(final String policyId, final String createdOnString, final String validUntilString) {
return Policy.builder()
.policyId(policyId)
.createdOn(DateUtils.toOffsetDateTimeAtStartOfDay(createdOnString))
.validUntil(DateUtils.toOffsetDateTimeAtEndOfDay(validUntilString))
.permissions(createPermissions())
.build();
}

private List<Permission> createPermissions(final PolicyType firstPermissionAction) {
return List.of(new Permission(firstPermissionAction, createConstraints()),
new Permission(PolicyType.ACCESS, createConstraints()));
}

private List<Permission> createPermissions() {
return List.of(new Permission(PolicyType.USE, createConstraints()),
new Permission(PolicyType.ACCESS, createConstraints()));
}

private Constraints createConstraints() {
return new Constraints(emptyList(), List.of(ConstraintConstants.ACTIVE_MEMBERSHIP,
ConstraintConstants.FRAMEWORK_AGREEMENT_TRACEABILITY_ACTIVE, ConstraintConstants.PURPOSE_ID_3_1_TRACE));
Expand Down
Loading