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/639 policy store api paging #754

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
258d1cd
feat(policy-api):[#639] Add a paged GET policies endpoint
dsmf Jul 2, 2024
297b19a
feat(policy-api):[#639] Fix one violation
dsmf Jul 2, 2024
25cdb2a
feat(policy-api):[#639] Extract pagination logic to own service in or…
dsmf Jul 2, 2024
dda6a40
feat(policy-api):[#639] Validation for max page size
dsmf Jul 2, 2024
0d4be46
Merge branch 'main' into feature/639-policy-store-api-paging
dsmf Jul 2, 2024
da6a7a6
feat(policy-api):[#639] Name todos
dsmf Jul 2, 2024
fa980bc
feat(policy-api):[#639] Implement multi-sort
dsmf Jul 3, 2024
c9ffe4d
feat(policy-api):[#639] Update CHANGELOG.md
dsmf Jul 3, 2024
b31a8e3
feat(policy-api):[#639] Remove unused code
dsmf Jul 3, 2024
9360ff0
feat(policy-api):[#639] split up method in order to reduce complexity
dsmf Jul 3, 2024
111885f
feat(policy-api):[#639] implement filtering and improvements
dsmf Jul 3, 2024
1af6633
feat(policy-api):[#639] fix some warnings
dsmf Jul 3, 2024
52d04b1
feat(policy-api):[#639] fix some warnings
dsmf Jul 3, 2024
8d7f8d1
Merge branch 'main' into feature/639-policy-store-api-paging
dsmf Jul 3, 2024
439ead5
feat(policy-api):[#639] update DEPENDENCIES
dsmf Jul 3, 2024
3f207f5
feat(policy-api):[#639] harmonized with old endpoint, simplified
dsmf Jul 3, 2024
4cad5fd
feat(policy-api):[#639] fix compatibility
dsmf Jul 4, 2024
8fbbab8
feat(policy-api):[#639] simplify PolicyResponse (deduplicate code)
dsmf Jul 4, 2024
66cdf87
feat(policy-api):[#639] fix NPE
dsmf Jul 4, 2024
5273fb5
feat(policy-api):[#639] cleanup
dsmf Jul 4, 2024
d4fcb7e
feat(policy-api):[#639] cleanup using static imports
dsmf Jul 4, 2024
6c2f9d4
feat(policy-api):[#639] simplified / cleanup
dsmf Jul 4, 2024
f3950f1
feat(policy-api):[#639] add TODO
dsmf Jul 4, 2024
7ec214f
feat(policy-api):[#639] update TODOs
dsmf Jul 4, 2024
105faaa
feat(policy-api):[#639] cleanup using static imports
dsmf Jul 4, 2024
521055b
feat(policy-api):[#639] use the action of the first permission for so…
dsmf Jul 4, 2024
5ad5fd6
feat(policy-api):[#639] cleanup using static imports
dsmf Jul 4, 2024
9665478
feat(policy-api):[#639] ignore PMD false positives
dsmf Jul 4, 2024
88fbf39
feat(policy-api):[#639] adjustments to prepared date operation for cr…
dsmf Jul 4, 2024
a3435f5
feat(policy-api):[#639] cleanup
dsmf Jul 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ _**For better traceability add the corresponding GitHub issue number in each cha
- Integration Test Policy Store API Unhappy Path. #519
- Support for SingleLevelUsageAsPlanned. #470
- Documentation to describe the delegate process. #470
- Added file for CC BY 4.0 license for TRG 7 #681
- Added file for CC BY 4.0 license for TRG 7. #681
- Paging for Policy Store API GET policies. #639
- New endpoint GET /irs/policies/paged.
- Supports sorting by multiple of the properties "bpn", "validUntil", "policyId", "createdOn", "action" with ascending / descending order.
- Supports filtering by multiple of properties "bpn", "validUntil", "policyId" (AND).
- note: filtering by "createdOn", "validUntil" has not been implemted yet

## [5.1.4] - 2024-05-27

Expand Down
6 changes: 3 additions & 3 deletions DEPENDENCIES
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ maven/mavencentral/dev.failsafe/failsafe/3.3.2, Apache-2.0, approved, #9268
maven/mavencentral/dk.brics.automaton/automaton/1.11-8, BSD-2-Clause, approved, clearlydefined
maven/mavencentral/io.burt/jmespath-core/0.5.1, BSD-3-Clause, approved, clearlydefined
maven/mavencentral/io.burt/jmespath-jackson/0.5.1, BSD-3-Clause, approved, clearlydefined
maven/mavencentral/io.cucumber/ci-environment/10.0.1, MIT, approved, #13863
maven/mavencentral/io.cucumber/ci-environment/10.0.1, MIT, approved, #15218
maven/mavencentral/io.cucumber/cucumber-core/7.18.0, MIT AND (Apache-2.0 AND MIT), approved, #15146
maven/mavencentral/io.cucumber/cucumber-expressions/17.1.0, MIT, approved, #14271
maven/mavencentral/io.cucumber/cucumber-gherkin-messages/7.18.0, MIT, approved, clearlydefined
Expand Down Expand Up @@ -225,7 +225,7 @@ maven/mavencentral/org.apache.logging.log4j/log4j-core/2.20.0, Apache-2.0 AND (A
maven/mavencentral/org.apache.logging.log4j/log4j-jul/2.20.0, Apache-2.0, approved, clearlydefined
maven/mavencentral/org.apache.logging.log4j/log4j-slf4j2-impl/2.20.0, Apache-2.0, approved, #8801
maven/mavencentral/org.apache.logging.log4j/log4j-to-slf4j/2.20.0, Apache-2.0, approved, #8799
maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-core/10.1.20, Apache-2.0 AND (EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0) AND (CDDL-1.0 OR GPL-2.0-only WITH Classpath-exception-2.0) AND W3C AND CC0-1.0, approved, #5949
maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-core/10.1.20, Apache-2.0 AND (EPL-2.0 OR (GPL-2.0 WITH Classpath-exception-2.0)) AND CDDL-1.0 AND (CDDL-1.1 OR (GPL-2.0-only WITH Classpath-exception-2.0)) AND EPL-2.0, approved, #15195
maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-el/10.1.20, Apache-2.0, approved, #6997
maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.20, Apache-2.0, approved, #7920
maven/mavencentral/org.apiguardian/apiguardian-api/1.1.2, Apache-2.0, approved, clearlydefined
Expand Down Expand Up @@ -413,7 +413,7 @@ maven/mavencentral/org.springframework/spring-webmvc/6.0.19, Apache-2.0, approve
maven/mavencentral/org.testcontainers/junit-jupiter/1.18.3, MIT, approved, #7941
maven/mavencentral/org.testcontainers/junit-jupiter/1.19.7, MIT, approved, #10344
maven/mavencentral/org.testcontainers/testcontainers/1.18.3, MIT, approved, #7938
maven/mavencentral/org.testcontainers/testcontainers/1.19.7, Apache-2.0 AND MIT, approved, #10347
maven/mavencentral/org.testcontainers/testcontainers/1.19.7, MIT, approved, #15203
maven/mavencentral/org.typelevel/spire-macros_2.13/0.17.0, MIT, approved, clearlydefined
maven/mavencentral/org.unbescape/unbescape/1.1.6.RELEASE, Apache-2.0, approved, CQ18904
maven/mavencentral/org.webjars/swagger-ui/5.2.0, Apache-2.0, approved, #10221
Expand Down
4 changes: 4 additions & 0 deletions irs-policy-store/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<dependency>
<groupId>net.javacrumbs.json-unit</groupId>
<artifactId>json-unit-assertj</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/********************************************************************************
* 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;

/**
* Common constants.
*/
public class CommonConstants {
public static final String PROPERTY_BPN = "bpn";
public static final String PROPERTY_POLICY_ID = "policyId";
public static final String PROPERTY_ACTION = "action";
public static final String PROPERTY_CREATED_ON = "createdOn";
public static final String PROPERTY_VALID_UNTIL = "validUntil";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/********************************************************************************
* 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.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 java.util.ArrayList;
import java.util.List;

import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.tractusx.irs.policystore.models.SearchCriteria;
import org.eclipse.tractusx.irs.policystore.models.SearchCriteria.Operation;

/**
* Parser for search parameters.
*/
@Getter
@SuppressWarnings({ "PMD.TooManyStaticImports" })
public class SearchParameterParser {

public static final List<String> SUPPORTED_PROPERTIES = List.of(PROPERTY_BPN, PROPERTY_VALID_UNTIL,
PROPERTY_POLICY_ID, PROPERTY_CREATED_ON, PROPERTY_ACTION);
public static final List<String> DATE_PROPERTIES = List.of(PROPERTY_VALID_UNTIL, PROPERTY_CREATED_ON);
public static final List<Operation> DATE_OPERATORS = List.of(BEFORE_LOCAL_DATE, AFTER_LOCAL_DATE);

public static final String CRITERIA_INNER_SEPARATOR = ",";
public static final int NUM_PARTS_OF_FILTERS = 3;

private final List<SearchCriteria<?>> searchCriteria;

public SearchParameterParser(final List<String> searchParameters) {
searchCriteria = parseSearchParameters(searchParameters);
}

private List<SearchCriteria<?>> parseSearchParameters(final List<String> searchParameterList) {
final List<SearchCriteria<?>> searchCriteria = new ArrayList<>();
if (searchParameterList != null) {
for (int i = 0; i < searchParameterList.size(); i++) {

final String searchParameter = searchParameterList.get(i);
final String[] splittedSearchParam = StringUtils.split(searchParameter, CRITERIA_INNER_SEPARATOR);

if (splittedSearchParam.length < NUM_PARTS_OF_FILTERS) {
throw new IllegalArgumentException(("Illegal search parameter at index %s. "
+ "Format should be <propertyName>,<operation>,<value>.").formatted(i));
}

final String property = getProperty(splittedSearchParam[0]);
final SearchCriteria.Operation operation = getOperation(splittedSearchParam[1], property);
final String value = getValue(splittedSearchParam[2]);

searchCriteria.add(new SearchCriteria<>(property, operation, value));
}
}

return searchCriteria;
}

private static String getValue(final String value) {
return StringUtils.trimToEmpty(value);
}

private static String getProperty(final String property) {
final String trimmedProperty = StringUtils.trimToEmpty(property);
if (SUPPORTED_PROPERTIES.stream().noneMatch(p -> p.equalsIgnoreCase(trimmedProperty))) {
throw new IllegalArgumentException("Only the following properties support filtering: %s".formatted(
String.join(", ", SUPPORTED_PROPERTIES)));
}
return trimmedProperty;
}

private Operation getOperation(final String operationStr, final String property) {
final Operation operation = Operation.valueOf(StringUtils.trimToEmpty(operationStr));
if (DATE_OPERATORS.contains(operation) && DATE_PROPERTIES.stream()
.noneMatch(p -> p.equalsIgnoreCase(property))) {
throw new IllegalArgumentException("Date operation are only supported for date properties");
}
return operation;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@

import static org.eclipse.tractusx.irs.common.ApiConstants.FORBIDDEN_DESC;
import static org.eclipse.tractusx.irs.common.ApiConstants.UNAUTHORIZED_DESC;
import static org.eclipse.tractusx.irs.policystore.common.CommonConstants.PROPERTY_BPN;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
Expand All @@ -50,14 +53,22 @@
import org.eclipse.tractusx.irs.data.JsonParseException;
import org.eclipse.tractusx.irs.dtos.ErrorResponse;
import org.eclipse.tractusx.irs.edc.client.policy.Policy;
import org.eclipse.tractusx.irs.policystore.common.SearchParameterParser;
import org.eclipse.tractusx.irs.policystore.models.CreatePoliciesResponse;
import org.eclipse.tractusx.irs.policystore.models.CreatePolicyRequest;
import org.eclipse.tractusx.irs.policystore.models.PolicyResponse;
import org.eclipse.tractusx.irs.policystore.models.PolicyWithBpn;
import org.eclipse.tractusx.irs.policystore.models.SearchCriteria;
import org.eclipse.tractusx.irs.policystore.models.UpdatePolicyRequest;
import org.eclipse.tractusx.irs.policystore.services.PolicyPagingService;
import org.eclipse.tractusx.irs.policystore.services.PolicyStoreService;
import org.eclipse.tractusx.irs.policystore.validators.BusinessPartnerNumberListValidator;
import org.eclipse.tractusx.irs.policystore.validators.ValidListOfBusinessPartnerNumbers;
import org.eclipse.tractusx.irs.policystore.validators.ValidPolicyId;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
Expand Down Expand Up @@ -88,9 +99,14 @@
public class PolicyStoreController {

public static final String BPN_REGEX = BusinessPartnerNumberListValidator.BPN_REGEX;
public static final int DEFAULT_PAGE_SIZE = 10;
public static final int MAX_PAGE_SIZE = 1000;
public static final String SEARCH = "search";

private final PolicyStoreService service;

private final PolicyPagingService policyPagingService;

private final HttpServletRequest httpServletRequest;

@Operation(operationId = "registerAllowedPolicy",
Expand Down Expand Up @@ -180,10 +196,8 @@ public Map<String, List<PolicyResponse>> getPolicies(//
) {

final Map<String, String[]> parameterMap = this.httpServletRequest.getParameterMap();
if (CollectionUtils.containsAny(parameterMap.keySet(), List.of("bpn", "bpns", "bpnls"))) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"Please use parameter 'businessPartnerNumbers' instead");
}

ensureParamBusinessPartnerNumberCorrectlyNamed(parameterMap);

final Map<String, List<Policy>> policies = service.getPolicies(businessPartnerNumbers);

Expand All @@ -194,6 +208,48 @@ public Map<String, List<PolicyResponse>> getPolicies(//
.collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
}

// TODO (mfischer): #639: add documentation and insomnia collection
@GetMapping("/policies/paged")
@ResponseStatus(HttpStatus.OK)
@PreAuthorize("hasAuthority('" + IrsRoles.ADMIN_IRS + "')")
public Page<PolicyResponse> getPoliciesPaged(//
@PageableDefault(size = DEFAULT_PAGE_SIZE, sort = PROPERTY_BPN, direction = Sort.Direction.ASC) //
final Pageable pageable, //
@RequestParam(required = false) //
@ValidListOfBusinessPartnerNumbers //
@Parameter(description = "List of business partner numbers.") //
final List<String> businessPartnerNumbers) {

if (pageable.getPageSize() > MAX_PAGE_SIZE) {
throw new IllegalArgumentException("Page size too large");
}

final Map<String, String[]> parameterMap = this.httpServletRequest.getParameterMap();

ensureParamBusinessPartnerNumberCorrectlyNamed(parameterMap);

// There seems to be a bug concerning interpretation of delimiters
// (https://stackoverflow.com/questions/37058691/encoded-comma-in-url-is-read-as-list-in-spring).
// The described annotation Delimiter did not work either. Therefore, we read the params manually from request:
final List<SearchCriteria<?>> searchCriteria = new SearchParameterParser(
getSearchParameters(parameterMap)).getSearchCriteria();
final Map<String, List<Policy>> bpnToPoliciesMap = service.getPolicies(businessPartnerNumbers);
final Page<PolicyWithBpn> policies = policyPagingService.getPolicies(bpnToPoliciesMap, pageable,
searchCriteria);
return policies.map(policyWithBpn -> PolicyResponse.from(policyWithBpn.policy(), policyWithBpn.bpn()));
}

private List<String> getSearchParameters(final Map<String, String[]> parameterMap) {
return parameterMap.get(SEARCH) != null ? Arrays.asList(parameterMap.get(SEARCH)) : Collections.emptyList();
}

private static void ensureParamBusinessPartnerNumberCorrectlyNamed(final Map<String, String[]> parameterMap) {
if (CollectionUtils.containsAny(parameterMap.keySet(), List.of("bpn", "bpns", "bpnls"))) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"Please use parameter 'businessPartnerNumbers' instead");
}
}

@Operation(operationId = "deleteAllowedPolicy",
summary = "Removes a policy that should no longer be accepted in EDC negotiation.",
security = @SecurityRequirement(name = "api_key"), tags = { "Item Relationship Service" },
Expand Down Expand Up @@ -252,8 +308,7 @@ public void deleteAllowedPolicy(@ValidPolicyId @PathVariable("policyId") final S
@DeleteMapping("/policies/{policyId}/bpnl/{bpnl}")
@ResponseStatus(HttpStatus.OK)
@PreAuthorize("hasAuthority('" + IrsRoles.ADMIN_IRS + "')")
public void removeAllowedPolicyFromBpnl(
@PathVariable("policyId") final String policyId, //
public void removeAllowedPolicyFromBpnl(@PathVariable("policyId") final String policyId, //
@Pattern(regexp = BPN_REGEX, message = " Invalid BPN.") //
@PathVariable("bpnl") final String bpnl) {
service.deletePolicyForEachBpn(policyId, List.of(bpnl));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import java.time.OffsetDateTime;

import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import org.eclipse.tractusx.irs.edc.client.policy.Policy;
Expand All @@ -30,7 +31,8 @@
*/
@Builder
@Schema(example = PolicyResponse.EXAMPLE_PAYLOAD)
public record PolicyResponse(OffsetDateTime validUntil, Payload payload) {
public record PolicyResponse(OffsetDateTime validUntil, Payload payload,
@JsonInclude(JsonInclude.Include.NON_NULL) String bpn) {

public static final String BPN_TO_POLICY_MAP_EXAMPLE = """
{
Expand Down Expand Up @@ -134,13 +136,19 @@ public record PolicyResponse(OffsetDateTime validUntil, Payload payload) {
""";

public static PolicyResponse fromPolicy(final Policy policy) {
return from(policy, null);
}

public static PolicyResponse from(final Policy policy, final String bpn) {
return PolicyResponse.builder()
.validUntil(policy.getValidUntil())
.payload(Payload.builder()
.policyId(policy.getPolicyId())
.context(Context.getDefault())
.policy(policy)
.build())
.bpn(bpn)
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/********************************************************************************
* 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.models;

import org.eclipse.tractusx.irs.edc.client.policy.Policy;

/**
* Helper record for remembering the BPN the policy belongs to.
*
* @param bpn the business partner number
* @param policy the policy
*/
public record PolicyWithBpn(String bpn, Policy policy) {

}
Loading
Loading