2 * ============LICENSE_START=======================================================
3 * Copyright (c) 2021 Bell Canada.
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.cps.temporal.controller.rest;
23 import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
24 import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
26 import java.util.List;
27 import java.util.stream.Collectors;
28 import org.onap.cps.temporal.controller.rest.model.AnchorDetails;
29 import org.onap.cps.temporal.controller.rest.model.AnchorDetailsMapper;
30 import org.onap.cps.temporal.controller.rest.model.AnchorHistory;
31 import org.onap.cps.temporal.controller.rest.model.SortMapper;
32 import org.onap.cps.temporal.controller.utils.DateTimeUtility;
33 import org.onap.cps.temporal.domain.NetworkData;
34 import org.onap.cps.temporal.domain.SearchCriteria;
35 import org.springframework.beans.factory.annotation.Value;
36 import org.springframework.data.domain.Pageable;
37 import org.springframework.data.domain.Slice;
38 import org.springframework.stereotype.Component;
39 import org.springframework.web.util.UriComponentsBuilder;
42 public class QueryResponseFactory {
44 private SortMapper sortMapper;
45 private String basePath;
46 private AnchorDetailsMapper anchorDetailsMapper;
51 * @param sortMapper sortMapper
52 * @param anchorDetailsMapper anchorDetailsMapper
53 * @param basePath basePath
55 public QueryResponseFactory(final SortMapper sortMapper,
56 final AnchorDetailsMapper anchorDetailsMapper,
57 @Value("${rest.api.base-path}") final String basePath) {
58 this.sortMapper = sortMapper;
59 this.anchorDetailsMapper = anchorDetailsMapper;
60 this.basePath = basePath;
63 AnchorHistory createAnchorsDataByFilterResponse(final SearchCriteria searchCriteria,
64 final Slice<NetworkData> response) {
66 final var anchorHistory = new AnchorHistory();
67 if (response.hasNext()) {
68 anchorHistory.setNextRecordsLink(
69 toRelativeLink(getAbsoluteLinkForGetAnchorsDataByFilter(searchCriteria, response.nextPageable())));
71 if (response.hasPrevious()) {
72 anchorHistory.setPreviousRecordsLink(
74 getAbsoluteLinkForGetAnchorsDataByFilter(searchCriteria, response.previousPageable())));
76 anchorHistory.setRecords(convertToAnchorDetails(response.getContent()));
80 AnchorHistory createAnchorDataByNameResponse(final SearchCriteria searchCriteria,
81 final Slice<NetworkData> response) {
83 final var anchorHistory = new AnchorHistory();
84 if (response.hasNext()) {
85 anchorHistory.setNextRecordsLink(toRelativeLink(
86 getAbsoluteLinkForGetAnchorDataByName(searchCriteria, response.nextPageable())));
88 if (response.hasPrevious()) {
89 anchorHistory.setPreviousRecordsLink(toRelativeLink(
90 getAbsoluteLinkForGetAnchorDataByName(searchCriteria, response.previousPageable())));
92 anchorHistory.setRecords(convertToAnchorDetails(response.getContent()));
96 private List<AnchorDetails> convertToAnchorDetails(final List<NetworkData> networkDataList) {
97 return networkDataList.stream()
98 .map(networkData -> anchorDetailsMapper.toAnchorDetails(networkData))
99 .collect(Collectors.toList());
103 Spring hateoas only provides absolute link. But in the microservices, relative links will be more appropriate
105 private String toRelativeLink(final String absoluteLink) {
107 /* Spring hateoas Issue:
108 It does replace the variable defined at the Controller level,
109 so we are removing the variable name and replace it with basePath.
110 https://github.com/spring-projects/spring-hateoas/issues/361
111 https://github.com/spring-projects/spring-hateoas/pull/1375
113 final int contextPathBeginIndex = absoluteLink.indexOf("rest.api.base-path%257D");
114 return basePath + absoluteLink.substring(contextPathBeginIndex + 23);
117 private String getAbsoluteLinkForGetAnchorDataByName(final SearchCriteria searchCriteria,
118 final Pageable pageable) {
119 final var uriComponentsBuilder = linkTo(methodOn(QueryController.class).getAnchorDataByName(
120 searchCriteria.getDataspaceName(),
121 searchCriteria.getAnchorName(),
122 DateTimeUtility.toString(searchCriteria.getObservedAfter()),
124 DateTimeUtility.toString(searchCriteria.getCreatedBefore()),
125 pageable.getPageNumber(), pageable.getPageSize(),
126 sortMapper.sortAsString(searchCriteria.getPageable().getSort())))
127 .toUriComponentsBuilder();
128 addSimplePayloadFilter(uriComponentsBuilder, searchCriteria.getSimplePayloadFilter());
129 return encodePlusSign(uriComponentsBuilder.toUriString());
132 private String getAbsoluteLinkForGetAnchorsDataByFilter(final SearchCriteria searchCriteria,
133 final Pageable pageable) {
134 final var uriComponentsBuilder = linkTo(methodOn(QueryController.class).getAnchorsDataByFilter(
135 searchCriteria.getDataspaceName(),
136 searchCriteria.getSchemaSetName(),
137 DateTimeUtility.toString(searchCriteria.getObservedAfter()),
139 DateTimeUtility.toString(searchCriteria.getCreatedBefore()),
140 pageable.getPageNumber(), pageable.getPageSize(),
141 sortMapper.sortAsString(searchCriteria.getPageable().getSort())))
142 .toUriComponentsBuilder();
143 addSimplePayloadFilter(uriComponentsBuilder, searchCriteria.getSimplePayloadFilter());
144 return encodePlusSign(uriComponentsBuilder.toUriString());
148 Spring hateoas does double encoding when generting URI.
149 To avoid it in the case of simplePayloadFilter,
150 the 'simplePayloadFilter is being added explicitly to UriComponentsBuilder
152 private UriComponentsBuilder addSimplePayloadFilter(final UriComponentsBuilder uriComponentsBuilder,
153 final String simplePayloadFilter) {
154 if (simplePayloadFilter != null) {
155 uriComponentsBuilder.queryParam("simplePayloadFilter", simplePayloadFilter);
157 return uriComponentsBuilder;
161 Spring hateoas does not encode '+' in the query param but it deccodes '+' as space.
162 Due to this inconsistency, API was failing to convert datetime with positive timezone.
163 The fix is done in the spring-hateoas 1.4 version but it is yet to release.
164 As a workaround, we are replacing all the '+' with '%2B'
165 https://github.com/spring-projects/spring-hateoas/issues/1485
167 private String encodePlusSign(final String link) {
168 return link.replace("+", "%2B");