Skip to content

Commit

Permalink
Merge pull request #754 from dsmf/feature/639-policy-store-api-paging
Browse files Browse the repository at this point in the history
Feature/639 policy store api paging
  • Loading branch information
ds-jhartmann authored Jul 8, 2024
2 parents 9436656 + a3435f5 commit 1b34b73
Show file tree
Hide file tree
Showing 13 changed files with 993 additions and 11 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,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
2 changes: 1 addition & 1 deletion DEPENDENCIES
Original file line number Diff line number Diff line change
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
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

0 comments on commit 1b34b73

Please sign in to comment.