Merge "Allow multiple versions of entities to be returned"
[policy/models.git] / models-base / src / main / java / org / onap / policy / models / base / PfConceptContainer.java
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2019 Nordix Foundation.
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.policy.models.base;
22
23 import java.lang.reflect.ParameterizedType;
24 import java.util.ArrayList;
25 import java.util.LinkedHashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 import java.util.NavigableMap;
30 import java.util.Set;
31 import java.util.TreeMap;
32
33 import javax.persistence.CascadeType;
34 import javax.persistence.EmbeddedId;
35 import javax.persistence.Entity;
36 import javax.persistence.FetchType;
37 import javax.persistence.ManyToMany;
38 import javax.persistence.Table;
39 import javax.ws.rs.core.Response;
40
41 import lombok.Data;
42 import lombok.EqualsAndHashCode;
43 import lombok.NonNull;
44
45 import org.onap.policy.common.utils.validation.Assertions;
46 import org.onap.policy.models.base.PfValidationResult.ValidationResult;
47
48 // @formatter:off
49 /**
50  * This class is a concept container and holds a map of concepts. The {@link PfConceptContainer} class implements the
51  * helper methods of the {@link PfConceptGetter} interface to allow {@link PfConceptContainer} instances to be retrieved
52  * by calling methods directly on this class without referencing the contained map.
53  *
54  * <p>Validation checks that a container key is not null. An error is issued if no concepts are defined in a container.
55  * Each concept entry is checked to ensure that its key and value are not null and that the key matches the key in the
56  * map value. Each concept entry is then validated individually.
57  *
58  * @param C the concept being contained
59  */
60 //@formatter:on
61 @Entity
62 @Table(name = "PfConceptContainer")
63 @Data
64 @EqualsAndHashCode(callSuper = false)
65
66 public class PfConceptContainer<C extends PfConcept, A extends PfNameVersion> extends PfConcept
67         implements PfConceptGetter<C>, PfAuthorative<List<Map<String, A>>> {
68     private static final long serialVersionUID = -324211738823208318L;
69
70     @EmbeddedId
71     private PfConceptKey key;
72
73     @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
74     private Map<PfConceptKey, C> conceptMap;
75
76     /**
77      * The Default Constructor creates a {@link PfConceptContainer} object with a null artifact key and creates an empty
78      * concept map.
79      */
80     public PfConceptContainer() {
81         this(new PfConceptKey());
82     }
83
84     /**
85      * The Key Constructor creates a {@link PfConceptContainer} object with the given artifact key and creates an empty
86      * concept map.
87      *
88      * @param key the concept key
89      */
90     public PfConceptContainer(@NonNull final PfConceptKey key) {
91         this(key, new TreeMap<PfConceptKey, C>());
92     }
93
94     /**
95      * This Constructor creates an concept container with all of its fields defined.
96      *
97      * @param key the concept container key
98      * @param conceptMap the concepts to be stored in the concept container
99      */
100     public PfConceptContainer(@NonNull final PfConceptKey key, @NonNull final Map<PfConceptKey, C> conceptMap) {
101         super();
102
103         this.key = key;
104         this.conceptMap = new TreeMap<>(conceptMap);
105     }
106
107     /**
108      * Copy constructor.
109      *
110      * @param copyConcept the concept to copy from
111      */
112     public PfConceptContainer(@NonNull final PfConceptContainer<C, A> copyConcept) {
113         super(copyConcept);
114     }
115
116     @Override
117     public List<PfKey> getKeys() {
118         final List<PfKey> keyList = key.getKeys();
119
120         for (final C concept : conceptMap.values()) {
121             keyList.addAll(concept.getKeys());
122         }
123
124         return keyList;
125     }
126
127     @Override
128     public List<Map<String, A>> toAuthorative() {
129         // The returned list is a list of map singletons with one map for each map
130         // entry in the concept container
131         List<Map<String, A>> toscaPolicyMapList = new ArrayList<>();
132
133         for (Entry<PfConceptKey, C> conceptEntry : getConceptMap().entrySet()) {
134             // Create a map to hold this entry
135             Map<String, A> toscaPolicyMap = new LinkedHashMap<>(1);
136
137             // Add the concept container entry to the singleton map
138             @SuppressWarnings("unchecked")
139             PfAuthorative<A> authoritiveImpl = (PfAuthorative<A>) conceptEntry.getValue();
140             toscaPolicyMap.put(conceptEntry.getKey().getName(), authoritiveImpl.toAuthorative());
141
142             // Add the map to the returned list
143             toscaPolicyMapList.add(toscaPolicyMap);
144         }
145
146         return toscaPolicyMapList;
147     }
148
149     @Override
150     public void fromAuthorative(List<Map<String, A>> authorativeList) {
151         // Clear any existing map entries
152         conceptMap.clear();
153
154         // Concepts are in lists of maps
155         for (Map<String, A> incomingConceptMap : authorativeList) {
156             // Add the map entries one by one
157             for (Entry<String, A> incomingConceptEntry : incomingConceptMap.entrySet()) {
158                 C jpaConcept = getConceptNewInstance();
159
160                 // This cast allows us to call the fromAuthorative method
161                 @SuppressWarnings("unchecked")
162                 PfAuthorative<A> authoritiveImpl = (PfAuthorative<A>) jpaConcept;
163
164                 if (incomingConceptEntry.getValue().getName() == null) {
165                     incomingConceptEntry.getValue().setName(incomingConceptEntry.getKey());
166                 }
167
168                 // Set the key name and the rest of the values on the concept
169                 authoritiveImpl.fromAuthorative(incomingConceptEntry.getValue());
170
171                 // This cast gets the key of the concept
172                 PfConceptKey conceptKey = (PfConceptKey) jpaConcept.getKey();
173
174                 // Set the concept key of the concept
175                 conceptKey.setName(incomingConceptEntry.getValue().getName());
176
177                 if (incomingConceptEntry.getValue().getVersion() != null) {
178                     conceptKey.setVersion(incomingConceptEntry.getValue().getVersion());
179                 }
180                 else {
181                     conceptKey.setVersion(PfKey.NULL_KEY_VERSION);
182                 }
183
184                 // After all that, save the map entry
185                 conceptMap.put(conceptKey, jpaConcept);
186             }
187         }
188
189         if (conceptMap.isEmpty()) {
190             throw new PfModelRuntimeException(Response.Status.BAD_REQUEST,
191                     "An incoming list of concepts must have at least one entry");
192         }
193     }
194
195     @Override
196     public void clean() {
197         key.clean();
198         for (final Entry<PfConceptKey, C> conceptEntry : conceptMap.entrySet()) {
199             conceptEntry.getKey().clean();
200             conceptEntry.getValue().clean();
201         }
202     }
203
204     @Override
205     public PfValidationResult validate(@NonNull final PfValidationResult resultIn) {
206         PfValidationResult result = resultIn;
207
208         if (key.equals(PfConceptKey.getNullKey())) {
209             result.addValidationMessage(
210                     new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID, "key is a null key"));
211         }
212
213         result = key.validate(result);
214
215         if (conceptMap.isEmpty()) {
216             result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
217                     "conceptMap may not be empty"));
218         } else {
219             result = validateConceptMap(result);
220         }
221
222         return result;
223     }
224
225     /**
226      * Validate the concept map of the container.
227      *
228      * @param resultIn the incoming validation results so far
229      * @return the validation results with the results of this validation added
230      */
231     private PfValidationResult validateConceptMap(final PfValidationResult resultIn) {
232         PfValidationResult result = resultIn;
233
234         for (final Entry<PfConceptKey, C> conceptEntry : conceptMap.entrySet()) {
235             if (conceptEntry.getKey().equals(PfConceptKey.getNullKey())) {
236                 result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
237                         "key on concept entry " + conceptEntry.getKey() + " may not be the null key"));
238             } else
239                 if (conceptEntry.getValue() == null) {
240                     result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
241                             "value on concept entry " + conceptEntry.getKey() + " may not be null"));
242                 } else
243                     if (!conceptEntry.getKey().equals(conceptEntry.getValue().getKey())) {
244                         result.addValidationMessage(new PfValidationMessage(key, this.getClass(),
245                                 ValidationResult.INVALID, "key on concept entry key " + conceptEntry.getKey()
246                                         + " does not equal concept value key " + conceptEntry.getValue().getKey()));
247                         result = conceptEntry.getValue().validate(result);
248                     } else {
249                         result = conceptEntry.getValue().validate(result);
250                     }
251         }
252         return result;
253     }
254
255     @Override
256     public int compareTo(final PfConcept otherConcept) {
257         if (otherConcept == null) {
258             return -1;
259         }
260         if (this == otherConcept) {
261             return 0;
262         }
263         if (getClass() != otherConcept.getClass()) {
264             return this.hashCode() - otherConcept.hashCode();
265         }
266
267         @SuppressWarnings("unchecked")
268         final PfConceptContainer<C, A> other = (PfConceptContainer<C, A>) otherConcept;
269         int retVal = key.compareTo(other.key);
270         if (retVal != 0) {
271             return retVal;
272         }
273
274         if (!conceptMap.equals(other.conceptMap)) {
275             return (conceptMap.hashCode() - other.conceptMap.hashCode());
276         }
277
278         return 0;
279     }
280
281     @Override
282     public PfConcept copyTo(@NonNull final PfConcept target) {
283         Assertions.instanceOf(target, PfConceptContainer.class);
284
285         @SuppressWarnings("unchecked")
286         final PfConceptContainer<C, A> copy = (PfConceptContainer<C, A>) target;
287         copy.setKey(new PfConceptKey(key));
288         final Map<PfConceptKey, C> newConceptMap = new TreeMap<>();
289         for (final Entry<PfConceptKey, C> conceptMapEntry : conceptMap.entrySet()) {
290             C newC = getConceptNewInstance();
291             conceptMapEntry.getValue().copyTo(newC);
292             newConceptMap.put(new PfConceptKey(conceptMapEntry.getKey()), newC);
293         }
294         copy.setConceptMap(newConceptMap);
295
296         return copy;
297     }
298
299     @Override
300     public C get(final PfConceptKey conceptKey) {
301         return new PfConceptGetterImpl<>((NavigableMap<PfConceptKey, C>) conceptMap).get(conceptKey);
302     }
303
304     @Override
305     public C get(final String conceptKeyName) {
306         return new PfConceptGetterImpl<>((NavigableMap<PfConceptKey, C>) conceptMap).get(conceptKeyName);
307     }
308
309     @Override
310     public C get(final String conceptKeyName, final String conceptKeyVersion) {
311         return new PfConceptGetterImpl<>((NavigableMap<PfConceptKey, C>) conceptMap).get(conceptKeyName,
312                 conceptKeyVersion);
313     }
314
315     @Override
316     public Set<C> getAll(final String conceptKeyName) {
317         return new PfConceptGetterImpl<>((NavigableMap<PfConceptKey, C>) conceptMap).getAll(conceptKeyName);
318     }
319
320     @Override
321     public Set<C> getAll(final String conceptKeyName, final String conceptKeyVersion) {
322         return new PfConceptGetterImpl<>((NavigableMap<PfConceptKey, C>) conceptMap).getAll(conceptKeyName,
323                 conceptKeyVersion);
324     }
325
326     /**
327      * Get a new empty instance of a concept for this concept map.
328      *
329      * @return the new instance
330      */
331     @SuppressWarnings("unchecked")
332     private C getConceptNewInstance() {
333         try {
334             String conceptClassName =
335                     ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0].getTypeName();
336             return (C) Class.forName(conceptClassName).newInstance();
337         } catch (Exception ex) {
338             throw new PfModelRuntimeException(Response.Status.INTERNAL_SERVER_ERROR,
339                     "failed to instantiate instance of container concept class", ex);
340         }
341     }
342 }