Java 17 / Spring 6 / Spring Boot 3 Upgrade
[policy/api.git] / main / src / main / java / org / onap / policy / api / main / startstop / ApiDatabaseInitializer.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP Policy API
4  * ================================================================================
5  * Copyright (C) 2019-2022 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2019-2021, 2023 Nordix Foundation.
7  * Modifications Copyright (C) 2022 Bell Canada. All rights reserved.
8  * ================================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  *
21  * SPDX-License-Identifier: Apache-2.0
22  * ============LICENSE_END=========================================================
23  */
24
25 package org.onap.policy.api.main.startstop;
26
27 import com.google.common.collect.Sets;
28 import jakarta.annotation.PostConstruct;
29 import java.util.ArrayList;
30 import java.util.LinkedHashMap;
31 import java.util.LinkedList;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Objects;
35 import lombok.RequiredArgsConstructor;
36 import org.onap.policy.api.main.config.PolicyPreloadConfig;
37 import org.onap.policy.api.main.exception.PolicyApiException;
38 import org.onap.policy.api.main.service.ToscaServiceTemplateService;
39 import org.onap.policy.common.utils.coder.CoderException;
40 import org.onap.policy.common.utils.coder.StandardYamlCoder;
41 import org.onap.policy.common.utils.resources.ResourceUtils;
42 import org.onap.policy.models.base.PfModelException;
43 import org.onap.policy.models.base.PfModelRuntimeException;
44 import org.onap.policy.models.tosca.authorative.concepts.ToscaEntity;
45 import org.onap.policy.models.tosca.authorative.concepts.ToscaEntityFilter;
46 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType;
47 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
48 import org.onap.policy.models.tosca.authorative.concepts.ToscaTopologyTemplate;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
52 import org.springframework.stereotype.Component;
53
54 /**
55  * This class creates initial policy types in the database.
56  *
57  * @author Chenfei Gao (cgao@research.att.com)
58  */
59 @Component
60 @ConditionalOnProperty(value = "database.initialize", havingValue = "true", matchIfMissing = true)
61 @RequiredArgsConstructor
62 public class ApiDatabaseInitializer {
63
64     private static final Logger LOGGER = LoggerFactory.getLogger(ApiDatabaseInitializer.class);
65     private static final StandardYamlCoder coder = new StandardYamlCoder();
66
67     private final ToscaServiceTemplateService toscaServiceTemplateService;
68     private final PolicyPreloadConfig policyPreloadConfig;
69
70     @PostConstruct
71     public void loadData() throws PolicyApiException {
72         initializeApiDatabase(policyPreloadConfig.getPolicyTypes(), policyPreloadConfig.getPolicies());
73     }
74
75     /**
76      * Initializes database by preloading policy types and policies.
77      *
78      * @param policyTypes List of policy types to preload.
79      * @param policies List of policies to preload.
80      * @throws PolicyApiException in case of errors.
81      */
82     public void initializeApiDatabase(final List<String> policyTypes, final List<String> policies)
83         throws PolicyApiException {
84         try {
85             if (alreadyExists()) {
86                 LOGGER.warn("DB already contains policy data - skipping preload");
87                 return;
88             }
89
90             var serviceTemplate = new ToscaServiceTemplate();
91             serviceTemplate.setDataTypes(new LinkedHashMap<>());
92             serviceTemplate.setPolicyTypes(new LinkedHashMap<>());
93             serviceTemplate.setToscaDefinitionsVersion("tosca_simple_yaml_1_1_0");
94
95             ToscaServiceTemplate createdPolicyTypes =
96                 preloadServiceTemplate(serviceTemplate, policyTypes, toscaServiceTemplateService::createPolicyType);
97             preloadServiceTemplate(createdPolicyTypes, policies, toscaServiceTemplateService::createPolicies);
98         } catch (final PolicyApiException | PfModelException | CoderException exp) {
99             throw new PolicyApiException(exp);
100         }
101     }
102
103     private boolean alreadyExists() throws PfModelException {
104         try {
105             ToscaServiceTemplate serviceTemplate = toscaServiceTemplateService
106                 .getFilteredPolicyTypes(ToscaEntityFilter.<ToscaPolicyType>builder().build());
107             if (!serviceTemplate.getPolicyTypes().isEmpty()) {
108                 return true;
109             }
110         } catch (PfModelRuntimeException e) {
111             LOGGER.trace("DB does not yet contain policy types", e);
112         }
113         return false;
114     }
115
116     private ToscaServiceTemplate preloadServiceTemplate(ToscaServiceTemplate serviceTemplate, List<String> entities,
117             FunctionWithEx<ToscaServiceTemplate, ToscaServiceTemplate> getter)
118             throws PolicyApiException, CoderException, PfModelException {
119
120         var multiVersionTemplates = new ArrayList<ToscaServiceTemplate>();
121
122         for (String entity : entities) {
123             ToscaServiceTemplate singleEntity = deserializeServiceTemplate(entity);
124
125             if (isMultiVersion(serviceTemplate.getPolicyTypes(), singleEntity.getPolicyTypes())) {
126                 // if this entity introduces a new policy version of an existing policy type,
127                 // process it on its own as continuing here will override the existing policy type
128                 // in a different version
129
130                 multiVersionTemplates.add(singleEntity);
131                 LOGGER.warn("Detected multi-versioned type: {}", entity);
132                 continue;
133             }
134
135             // Consolidate data types and policy types
136             if (singleEntity.getDataTypes() != null) {
137                 serviceTemplate.getDataTypes().putAll(singleEntity.getDataTypes());
138             }
139             if (singleEntity.getPolicyTypes() != null) {
140                 serviceTemplate.getPolicyTypes().putAll(singleEntity.getPolicyTypes());
141             }
142
143             // Consolidate policies
144             var topologyTemplate = singleEntity.getToscaTopologyTemplate();
145             if (topologyTemplate != null && topologyTemplate.getPolicies() != null) {
146                 serviceTemplate.setToscaTopologyTemplate(new ToscaTopologyTemplate());
147                 serviceTemplate.getToscaTopologyTemplate().setPolicies(new LinkedList<>());
148                 serviceTemplate.getToscaTopologyTemplate().getPolicies()
149                         .addAll(singleEntity.getToscaTopologyTemplate().getPolicies());
150             }
151         }
152         // Preload the specified entities
153         ToscaServiceTemplate createdServiceTemplate = getter.apply(serviceTemplate);
154         LOGGER.debug("Created initial tosca service template in DB - {}", createdServiceTemplate);
155
156         multiVersionTemplates
157             .forEach(mvServiceTemplate -> {
158                 try {
159                     LOGGER.info("Multi-versioned Service Template {}", mvServiceTemplate.getPolicyTypes().keySet());
160                     getter.apply(mvServiceTemplate);
161                 } catch (PfModelException e) {
162                     LOGGER.warn("ToscaServiceTemple cannot be preloaded: {}", mvServiceTemplate, e);
163                 }
164             });
165         return createdServiceTemplate;
166     }
167
168     private ToscaServiceTemplate deserializeServiceTemplate(String entity) throws PolicyApiException, CoderException {
169         var entityAsStringYaml = ResourceUtils.getResourceAsString(entity);
170         if (entityAsStringYaml == null) {
171             throw new PolicyApiException("Preloaded entity cannot be found " + entity);
172         }
173
174         ToscaServiceTemplate singleEntity = coder.decode(entityAsStringYaml, ToscaServiceTemplate.class);
175         if (singleEntity == null) {
176             throw new PolicyApiException("Error deserializing entity from file: " + entity);
177         }
178         return singleEntity;
179     }
180
181     // This method is templated, so it can be used with other derivations of ToscaEntity in the future,
182     // if multi-version are desired.
183
184     protected <T extends ToscaEntity> boolean isMultiVersion(Map<String, T> aggEntity,
185                                                              Map<String, T> singleEntity) {
186         if (aggEntity == null || singleEntity == null) {
187             return false;
188         }
189
190         // There is a multi-version entity if both key sets have the same
191         // entity name but different version.
192
193         return
194             Sets.intersection(aggEntity.keySet(), singleEntity.keySet())
195                 .stream()
196                 .anyMatch(e -> !Objects.equals(aggEntity.get(e).getVersion(), singleEntity.get(e).getVersion()));
197     }
198
199     @FunctionalInterface
200     protected interface FunctionWithEx<T, R> {
201         R apply(T value) throws PfModelException;
202     }
203 }