8157d91d4c11492b0b520df1b736283878e73942
[policy/apex-pdp.git] / model / policy-model / src / main / java / org / onap / policy / apex / model / policymodel / concepts / AxPolicy.java
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2016-2018 Ericsson. All rights reserved.
4  *  Modifications Copyright (C) 2019 Nordix Foundation.
5  * ================================================================================
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * SPDX-License-Identifier: Apache-2.0
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.policy.apex.model.policymodel.concepts;
23
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Map.Entry;
27 import java.util.Set;
28 import java.util.TreeMap;
29 import java.util.TreeSet;
30
31 import javax.persistence.CascadeType;
32 import javax.persistence.Column;
33 import javax.persistence.EmbeddedId;
34 import javax.persistence.Entity;
35 import javax.persistence.JoinColumn;
36 import javax.persistence.JoinTable;
37 import javax.persistence.OneToMany;
38 import javax.persistence.Table;
39 import javax.xml.bind.annotation.XmlAccessType;
40 import javax.xml.bind.annotation.XmlAccessorType;
41 import javax.xml.bind.annotation.XmlElement;
42 import javax.xml.bind.annotation.XmlRootElement;
43 import javax.xml.bind.annotation.XmlType;
44
45 import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
46 import org.onap.policy.apex.model.basicmodel.concepts.AxConcept;
47 import org.onap.policy.apex.model.basicmodel.concepts.AxKey;
48 import org.onap.policy.apex.model.basicmodel.concepts.AxReferenceKey;
49 import org.onap.policy.apex.model.basicmodel.concepts.AxValidationMessage;
50 import org.onap.policy.apex.model.basicmodel.concepts.AxValidationResult;
51 import org.onap.policy.apex.model.basicmodel.concepts.AxValidationResult.ValidationResult;
52 import org.onap.policy.common.utils.validation.Assertions;
53 import org.slf4j.ext.XLogger;
54 import org.slf4j.ext.XLoggerFactory;
55
56 /**
57  * This class holds the definition of an Apex policy. A policy is made up of a tree of states, each represented by an
58  * {@link AxState} instance. The states of a policy are held in a map in the policy. The state tree is built up at
59  * policy design time by a policy editor and each state is connected to its next state by an {@link AxStateOutput}
60  * instance.
61  *
62  * <p>Execution of a policy is triggered by an event. A policy starts execution from its first state so the trigger
63  * event for the first sate is the trigger event for the entire policy. Execution from that first state can continue to
64  * one or more subsequent states and so on down branches of states. The state output of the final state in a branch has
65  * no next state, indicating the end of execution of that branch. Therefore, the set of output events from final states
66  * in the policy are the possible set of output events on the policy. A state may only be used once in the state tree of
67  * a policy and recursive execution of states in the same execution branch is not allowed, so the same state may not
68  * execute more than once on a single execution of a policy.
69  *
70  * <p>The template of a policy is a string that can be used by policy editors to store meta information on the policy
71  * that can be used at design time. The policy template string is not used during policy execution.
72  *
73  * <p>During validation of a policy, the validation checks listed below are executed: <ol> <li>The policy key must not
74  * be a null key <li>The policy key must be valid <li>If the policy template is not set, an observation is issued <li>At
75  * least one state must be defined <li>Keys and values must all be defined, that is not null <li>The key on each entry
76  * in the state map must match the key in the entry's value <li>The parent key of each state in the state map of a
77  * policy must be the key of that policy <li>Each state must itself be valid, see validation in {@link AxState} <li>The
78  * next state of the state output of each state must be defined as a state in the policy <li>The first state of a policy
79  * must be set <li>The first state of a policy must be defined in the policy <li>If a state is defined but is not used
80  * in a policy,a warning is issued <li>The state tree of the policy must be valid, see validation in {@link AxStateTree}
81  * </ol>
82  */
83
84 @Entity
85 @Table(name = "AxPolicy")
86 @XmlRootElement(name = "apexPolicy", namespace = "http://www.onap.org/policy/apex-pdp")
87 @XmlAccessorType(XmlAccessType.FIELD)
88 @XmlType(name = "AxPolicy", namespace = "http://www.onap.org/policy/apex-pdp", propOrder =
89     { "key", "template", "stateMap", "firstState" })
90
91 public class AxPolicy extends AxConcept {
92     private static final long serialVersionUID = -1775614096390365941L;
93
94     // Logger for this class
95     private static final XLogger LOGGER = XLoggerFactory.getXLogger(AxPolicy.class);
96
97     @EmbeddedId
98     @XmlElement(name = "policyKey", required = true)
99     private AxArtifactKey key;
100
101     @Column(name = "template")
102     @XmlElement(required = true)
103     private String template;
104
105     // @formatter:off
106     @OneToMany(cascade = CascadeType.ALL)
107     @JoinTable(joinColumns = {@JoinColumn(name = "parentKeyName", referencedColumnName = "name"),
108             @JoinColumn(name = "parentKeyVersion", referencedColumnName = "version")})
109     @XmlElement(name = "state", required = true)
110     private Map<String, AxState> stateMap;
111     // @formatter:on
112
113     @Column(name = "firstState")
114     @XmlElement(required = true)
115     private String firstState;
116
117     /**
118      * The Default Constructor creates a policy instance with a null key, a blank template and undefined first state.
119      */
120     public AxPolicy() {
121         this(new AxArtifactKey());
122     }
123
124     /**
125      * Copy constructor.
126      *
127      * @param copyConcept the concept to copy from
128      */
129     public AxPolicy(final AxPolicy copyConcept) {
130         super(copyConcept);
131     }
132
133     /**
134      * The Key Constructor creates a policy instance with the given key, a blank template and undefined first state.
135      *
136      * @param key the key of the policy
137      */
138     public AxPolicy(final AxArtifactKey key) {
139         this(key, "", new TreeMap<String, AxState>(), "");
140     }
141
142     /**
143      * This Constructor creates a policy with the given key and all its fields defined.
144      *
145      * @param key the key of the policy
146      * @param template the policy template for policy editor metadata
147      * @param stateMap the state map containing the states of the policy
148      * @param firstState the first state that will execute on this policy
149      */
150     public AxPolicy(final AxArtifactKey key, final String template, final Map<String, AxState> stateMap,
151                     final String firstState) {
152         super();
153         Assertions.argumentNotNull(key, "key may not be null");
154         Assertions.argumentNotNull(template, "template may not be null");
155         Assertions.argumentNotNull(stateMap, "stateMap may not be null");
156         Assertions.argumentNotNull(firstState, "firstState may not be null");
157
158         this.key = key;
159         this.template = template;
160         this.stateMap = stateMap;
161         this.firstState = firstState;
162     }
163
164     /**
165      * Gets a tree that holds all the possible execution paths for this policy. This method may be used for verification
166      * of policies, to find the branches of policy execution and the final states of policies.
167      *
168      * @return the state tree of the policy, a tree representing the execution branches of the policy
169      */
170     public AxStateTree getStateTree() {
171         return new AxStateTree(this, stateMap.get(firstState), null);
172     }
173
174     /**
175      * {@inheritDoc}.
176      */
177     @Override
178     public AxArtifactKey getKey() {
179         return key;
180     }
181
182     /**
183      * {@inheritDoc}.
184      */
185     @Override
186     public List<AxKey> getKeys() {
187         final List<AxKey> keyList = key.getKeys();
188         for (final AxState state : stateMap.values()) {
189             keyList.addAll(state.getKeys());
190         }
191         return keyList;
192     }
193
194     /**
195      * Sets the key of the policy.
196      *
197      * @param key the key of the policy
198      */
199     public void setKey(final AxArtifactKey key) {
200         Assertions.argumentNotNull(key, "key may not be null");
201         this.key = key;
202     }
203
204     /**
205      * Gets the policy template for policy editor metadata.
206      *
207      * @return the policy template for policy editor metadata
208      */
209     public String getTemplate() {
210         return template;
211     }
212
213     /**
214      * Sets the policy template for policy editor metadata.
215      *
216      * @param template the policy template for policy editor metadata
217      */
218     public void setTemplate(final String template) {
219         Assertions.argumentNotNull(template, "template may not be null");
220         this.template = template;
221     }
222
223     /**
224      * Gets a map containing the states of the policy.
225      *
226      * @return the map of states in the policy
227      */
228     public Map<String, AxState> getStateMap() {
229         return stateMap;
230     }
231
232     /**
233      * Sets a map containing the states of the policy.
234      *
235      * @param stateMap a map of states in the policy
236      */
237     public void setStateMap(final Map<String, AxState> stateMap) {
238         Assertions.argumentNotNull(stateMap, "stateMap may not be null");
239         this.stateMap = stateMap;
240     }
241
242     /**
243      * Gets the first state of the policy.
244      *
245      * @return the first state of the policy
246      */
247     public String getFirstState() {
248         return firstState;
249     }
250
251     /**
252      * Sets the first state of the policy.
253      *
254      * @param firstState the first state of the policy
255      */
256     public void setFirstState(final String firstState) {
257         Assertions.argumentNotNull(firstState, "firstState may not be null");
258         this.firstState = firstState;
259     }
260
261     /**
262      * {@inheritDoc}.
263      */
264     @Override
265     public AxValidationResult validate(final AxValidationResult resultIn) {
266         AxValidationResult result = resultIn;
267
268         if (key.equals(AxArtifactKey.getNullKey())) {
269             result.addValidationMessage(new AxValidationMessage(key, this.getClass(), ValidationResult.INVALID,
270                             "key is a null key"));
271         }
272
273         result = key.validate(result);
274
275         if (template.trim().length() == 0) {
276             result.addValidationMessage(new AxValidationMessage(key, this.getClass(), ValidationResult.OBSERVATION,
277                             "a policy template has not been specified"));
278         }
279
280         if (stateMap.size() == 0) {
281             result.addValidationMessage(new AxValidationMessage(key, this.getClass(), ValidationResult.INVALID,
282                             "stateMap may not be empty"));
283         } else {
284             for (final Entry<String, AxState> stateEntry : stateMap.entrySet()) {
285                 result = validateStateEntry(stateEntry, result);
286             }
287         }
288
289         // Validation continues from this point only if all validation checks this far have been
290         // passed
291         if (!result.isOk()) {
292             return result;
293         }
294
295         // We only check the unused states on models validated this far
296         if (firstState.trim().length() == 0) {
297             result.addValidationMessage(new AxValidationMessage(key, this.getClass(), ValidationResult.INVALID,
298                             "no first state specified, first state may not be blank"));
299         } else {
300             if (!stateMap.containsKey(firstState)) {
301                 result.addValidationMessage(new AxValidationMessage(key, this.getClass(), ValidationResult.INVALID,
302                                 "first state not found in stateMap"));
303             } else {
304                 validateStateTree(result);
305             }
306         }
307
308         return result;
309     }
310
311     /**
312      * Validate a state entry.
313      *
314      * @param stateEntry the state entry to validate
315      * @param result The validation result to append to
316      * @return The result of the validation
317      */
318     private AxValidationResult validateStateEntry(final Entry<String, AxState> stateEntry, AxValidationResult result) {
319         if (stateEntry.getKey() == null || stateEntry.getKey().equals(AxKey.NULL_KEY_NAME)) {
320             result.addValidationMessage(new AxValidationMessage(key, this.getClass(), ValidationResult.INVALID,
321                             "key on state entry key " + stateEntry.getKey() + " may not be the null key"));
322             return result;
323         }
324
325         if (stateEntry.getValue() == null) {
326             result.addValidationMessage(new AxValidationMessage(key, this.getClass(), ValidationResult.INVALID,
327                             "value on state entry value " + stateEntry.getKey() + " may not be null"));
328             return result;
329         }
330
331         if (!stateEntry.getKey().equals(stateEntry.getValue().getKey().getLocalName())) {
332             result.addValidationMessage(new AxValidationMessage(key, this.getClass(), ValidationResult.INVALID,
333                             "key on state entry key " + stateEntry.getKey()
334                                             + " does not equal state entry value local name "
335                                             + stateEntry.getValue().getKey().getLocalName()));
336         }
337
338         if (!stateEntry.getValue().getKey().getParentArtifactKey().equals(key)) {
339             result.addValidationMessage(new AxValidationMessage(key, this.getClass(), ValidationResult.INVALID,
340                             "parent key on state entry key " + stateEntry.getValue().getKey()
341                                             + " does not equal policy key"));
342         }
343
344         result = stateEntry.getValue().validate(result);
345
346         for (final AxStateOutput stateOutput : stateEntry.getValue().getStateOutputs().values()) {
347             if (!stateOutput.getNextState().equals(AxReferenceKey.getNullKey())
348                             && !stateMap.containsKey(stateOutput.getNextState().getLocalName())) {
349                 result.addValidationMessage(new AxValidationMessage(key, this.getClass(), ValidationResult.INVALID,
350                                 " nextState of state " + stateEntry.getKey() + " not found in StateMap: "
351                                                 + stateOutput.getNextState().getId()));
352             }
353         }
354
355         return result;
356     }
357
358     /**
359      * Validate a state tree to ensure there are no circular references in it.
360      *
361      * @param result The validation result to append to
362      * @return The result of the validation
363      */
364     private AxValidationResult validateStateTree(AxValidationResult result) {
365         try {
366             // Constructor validates policy state tree
367             AxStateTree policyStateTree = getStateTree();
368
369             // Check for unused states
370             final Set<AxState> referencedStateSet = policyStateTree.getReferencedStateSet();
371             final Set<AxState> unreferencedStateSet = new TreeSet<>(stateMap.values());
372             unreferencedStateSet.removeAll(referencedStateSet);
373
374             for (final AxState unreferencedState : unreferencedStateSet) {
375                 result.addValidationMessage(new AxValidationMessage(key, this.getClass(), ValidationResult.WARNING,
376                                 "state " + unreferencedState.getKey()
377                                                 + " is not referenced in the policy execution tree"));
378             }
379         } catch (PolicyRuntimeException pre) {
380             AxValidationMessage validationMessage = new AxValidationMessage(key, this.getClass(),
381                             ValidationResult.WARNING, "state tree in policy is invalid");
382             LOGGER.trace(validationMessage.getMessage(), pre);
383             result.addValidationMessage(validationMessage);
384         }
385
386         return result;
387     }
388
389     /**
390      * {@inheritDoc}.
391      */
392     @Override
393     public void clean() {
394         key.clean();
395         firstState = firstState.trim();
396     }
397
398     /**
399      * {@inheritDoc}.
400      */
401     @Override
402     public String toString() {
403         final StringBuilder builder = new StringBuilder();
404         builder.append(this.getClass().getSimpleName());
405         builder.append(":(");
406         builder.append("key=");
407         builder.append(key);
408         builder.append(",template=");
409         builder.append(template);
410         builder.append(",stateMap=");
411         builder.append(stateMap);
412         builder.append(",firstState=");
413         builder.append(firstState);
414         builder.append(")");
415         return builder.toString();
416     }
417
418     /**
419      * {@inheritDoc}.
420      */
421     @Override
422     public AxConcept copyTo(final AxConcept targetObject) {
423         Assertions.argumentNotNull(targetObject, "target may not be null");
424
425         final Object copyObject = targetObject;
426         Assertions.instanceOf(copyObject, AxPolicy.class);
427
428         final AxPolicy copy = ((AxPolicy) copyObject);
429         copy.setKey(new AxArtifactKey(key));
430         copy.setTemplate(template);
431
432         final Map<String, AxState> newStateMap = new TreeMap<>();
433         for (final Entry<String, AxState> stateMapEntry : stateMap.entrySet()) {
434             newStateMap.put(stateMapEntry.getKey(), new AxState(stateMapEntry.getValue()));
435         }
436         copy.setStateMap(newStateMap);
437
438         copy.setFirstState(firstState);
439
440         return copy;
441     }
442
443     /**
444      * {@inheritDoc}.
445      */
446     @Override
447     public int hashCode() {
448         final int prime = 31;
449         int result = 1;
450         result = prime * result + key.hashCode();
451         result = prime * result + template.hashCode();
452         result = prime * result + stateMap.hashCode();
453         result = prime * result + firstState.hashCode();
454         return result;
455     }
456
457     /**
458      * {@inheritDoc}.
459      */
460     @Override
461     public boolean equals(final Object obj) {
462         if (obj == null) {
463             return false;
464         }
465         if (this == obj) {
466             return true;
467         }
468         if (getClass() != obj.getClass()) {
469             return false;
470         }
471
472         final AxPolicy other = (AxPolicy) obj;
473         if (!key.equals(other.key)) {
474             return false;
475         }
476         if (!template.equals(other.template)) {
477             return false;
478         }
479         if (!stateMap.equals(other.stateMap)) {
480             return false;
481         }
482         return firstState.equals(other.firstState);
483     }
484
485     /**
486      * {@inheritDoc}.
487      */
488     @Override
489     public int compareTo(final AxConcept otherObj) {
490         if (otherObj == null) {
491             return -1;
492         }
493         if (this == otherObj) {
494             return 0;
495         }
496         if (getClass() != otherObj.getClass()) {
497             return this.hashCode() - otherObj.hashCode();
498         }
499
500         final AxPolicy other = (AxPolicy) otherObj;
501         if (!key.equals(other.key)) {
502             return key.compareTo(other.key);
503         }
504         if (!template.equals(other.template)) {
505             return template.compareTo(other.template);
506         }
507         if (!stateMap.equals(other.stateMap)) {
508             return (stateMap.hashCode() - other.stateMap.hashCode());
509         }
510         return firstState.compareTo(other.firstState);
511     }
512 }