6ac47599d6e67c32fa9d61a4452862c057a1efef
[cps/cps-temporal.git] / src / main / java / org / onap / cps / temporal / controller / rest / QueryResponseFactory.java
1 /*
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
8  *
9  *         http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  *
17  * SPDX-License-Identifier: Apache-2.0
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.cps.temporal.controller.rest;
22
23 import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
24 import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
25
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;
40
41 @Component
42 public class QueryResponseFactory {
43
44     private SortMapper sortMapper;
45     private String basePath;
46     private AnchorDetailsMapper anchorDetailsMapper;
47
48     /**
49      * Constructor.
50      *
51      * @param sortMapper          sortMapper
52      * @param anchorDetailsMapper anchorDetailsMapper
53      * @param basePath            basePath
54      */
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;
61     }
62
63     AnchorHistory createAnchorsDataByFilterResponse(final SearchCriteria searchCriteria,
64         final Slice<NetworkData> response) {
65
66         final var anchorHistory = new AnchorHistory();
67         if (response.hasNext()) {
68             anchorHistory.setNextRecordsLink(
69                 toRelativeLink(getAbsoluteLinkForGetAnchorsDataByFilter(searchCriteria, response.nextPageable())));
70         }
71         if (response.hasPrevious()) {
72             anchorHistory.setPreviousRecordsLink(
73                 toRelativeLink(
74                     getAbsoluteLinkForGetAnchorsDataByFilter(searchCriteria, response.previousPageable())));
75         }
76         anchorHistory.setRecords(convertToAnchorDetails(response.getContent()));
77         return anchorHistory;
78     }
79
80     AnchorHistory createAnchorDataByNameResponse(final SearchCriteria searchCriteria,
81         final Slice<NetworkData> response) {
82
83         final var anchorHistory = new AnchorHistory();
84         if (response.hasNext()) {
85             anchorHistory.setNextRecordsLink(toRelativeLink(
86                 getAbsoluteLinkForGetAnchorDataByName(searchCriteria, response.nextPageable())));
87         }
88         if (response.hasPrevious()) {
89             anchorHistory.setPreviousRecordsLink(toRelativeLink(
90                 getAbsoluteLinkForGetAnchorDataByName(searchCriteria, response.previousPageable())));
91         }
92         anchorHistory.setRecords(convertToAnchorDetails(response.getContent()));
93         return anchorHistory;
94     }
95
96     private List<AnchorDetails> convertToAnchorDetails(final List<NetworkData> networkDataList) {
97         return networkDataList.stream()
98             .map(networkData -> anchorDetailsMapper.toAnchorDetails(networkData))
99             .collect(Collectors.toList());
100     }
101
102     /*
103     Spring hateoas only provides absolute link. But in the microservices, relative links will be more appropriate
104      */
105     private String toRelativeLink(final String absoluteLink) {
106
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
112          */
113         final int contextPathBeginIndex = absoluteLink.indexOf("rest.api.base-path%257D");
114         return basePath + absoluteLink.substring(contextPathBeginIndex + 23);
115     }
116
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()),
123             null,
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());
130     }
131
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()),
138             null,
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());
145     }
146
147     /*
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
151      */
152     private UriComponentsBuilder addSimplePayloadFilter(final UriComponentsBuilder uriComponentsBuilder,
153         final String simplePayloadFilter) {
154         if (simplePayloadFilter != null) {
155             uriComponentsBuilder.queryParam("simplePayloadFilter", simplePayloadFilter);
156         }
157         return uriComponentsBuilder;
158     }
159
160     /*
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
166      */
167     private String encodePlusSign(final String link) {
168         return link.replace("+", "%2B");
169     }
170 }