0d1daaa674b85426eb3bd14b4fad91fcb2ae18b8
[policy/xacml-pdp.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  *
19  * SPDX-License-Identifier: Apache-2.0
20  * ============LICENSE_END=========================================================
21  */
22
23 package org.onap.policy.pdp.xacml.application.common.std;
24
25 import com.att.research.xacml.api.Identifier;
26 import com.att.research.xacml.api.Obligation;
27 import com.att.research.xacml.api.Request;
28 import com.att.research.xacml.api.XACML3;
29 import com.att.research.xacml.std.IdentifierImpl;
30 import com.att.research.xacml.util.XACMLPolicyWriter;
31 import java.io.ByteArrayOutputStream;
32 import java.io.IOException;
33 import java.nio.charset.StandardCharsets;
34 import java.nio.file.Files;
35 import java.nio.file.Path;
36 import java.nio.file.Paths;
37 import java.util.Arrays;
38 import java.util.Collection;
39 import java.util.HashMap;
40 import java.util.LinkedHashMap;
41 import java.util.LinkedList;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Map.Entry;
45 import lombok.Setter;
46 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AnyOfType;
47 import oasis.names.tc.xacml._3_0.core.schema.wd_17.EffectType;
48 import oasis.names.tc.xacml._3_0.core.schema.wd_17.MatchType;
49 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType;
50 import oasis.names.tc.xacml._3_0.core.schema.wd_17.RuleType;
51 import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType;
52 import org.apache.commons.lang3.tuple.Pair;
53 import org.onap.policy.common.endpoints.parameters.RestServerParameters;
54 import org.onap.policy.common.utils.coder.CoderException;
55 import org.onap.policy.common.utils.coder.StandardCoder;
56 import org.onap.policy.common.utils.coder.StandardYamlCoder;
57 import org.onap.policy.models.decisions.concepts.DecisionRequest;
58 import org.onap.policy.models.decisions.concepts.DecisionResponse;
59 import org.onap.policy.models.tosca.authorative.concepts.ToscaDataType;
60 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
61 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType;
62 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
63 import org.onap.policy.models.tosca.authorative.concepts.ToscaProperty;
64 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
65 import org.onap.policy.pdp.xacml.application.common.OnapObligation;
66 import org.onap.policy.pdp.xacml.application.common.PolicyApiCaller;
67 import org.onap.policy.pdp.xacml.application.common.PolicyApiException;
68 import org.onap.policy.pdp.xacml.application.common.ToscaDictionary;
69 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConversionException;
70 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslatorUtils;
71 import org.onap.policy.pdp.xacml.application.common.XacmlApplicationException;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74
75 /**
76  * This standard matchable translator uses Policy Types that contain "matchable" field in order
77  * to translate policies.
78  *
79  * @author pameladragosh
80  *
81  */
82 public class StdMatchableTranslator  extends StdBaseTranslator {
83
84     private static final Logger LOGGER = LoggerFactory.getLogger(StdMatchableTranslator.class);
85     private static final StandardYamlCoder standardYamlCoder = new StandardYamlCoder();
86
87     private final Map<ToscaPolicyTypeIdentifier, ToscaServiceTemplate> matchablePolicyTypes = new HashMap<>();
88     @Setter
89     private RestServerParameters apiRestParameters;
90     @Setter
91     private Path pathForData;
92
93     public StdMatchableTranslator() {
94         super();
95     }
96
97     @Override
98     public Request convertRequest(DecisionRequest request) {
99         LOGGER.info("Converting Request {}", request);
100         try {
101             return StdMatchablePolicyRequest.createInstance(request);
102         } catch (XacmlApplicationException e) {
103             LOGGER.error("Failed to convert DecisionRequest: {}", e);
104         }
105         //
106         // TODO throw exception
107         //
108         return null;
109     }
110
111     /**
112      * scanObligations - scans the list of obligations and make appropriate method calls to process
113      * obligations.
114      *
115      * @param obligations Collection of obligation objects
116      * @param decisionResponse DecisionResponse object used to store any results from obligations.
117      */
118     @Override
119     protected void scanObligations(Collection<Obligation> obligations, DecisionResponse decisionResponse) {
120         //
121         // Implementing a crude "closest match" on the results, which means we will strip out
122         // any policies that has the lower weight than any of the others.
123         //
124         // Most likely these are "default" policies with a weight of zero, but not always.
125         //
126         // It is possible to have multiple policies with an equal weight, that is desired.
127         //
128         // So we need to track each policy type separately and the weights for each policy.
129         //
130         // policy-type -> weight -> List({policy-id, policy-content}, {policy-id, policy-content})
131         //
132         Map<String, Map<Integer, List<Pair<String, Map<String, Object>>>>> closestMatches = new LinkedHashMap<>();
133         //
134         // Now scan the list of obligations
135         //
136         for (Obligation obligation : obligations) {
137             Identifier obligationId = obligation.getId();
138             LOGGER.info("Obligation: {}", obligationId);
139             if (ToscaDictionary.ID_OBLIGATION_REST_BODY.equals(obligationId)) {
140                 scanClosestMatchObligation(closestMatches, obligation);
141             } else {
142                 LOGGER.warn("Unsupported Obligation Id {}", obligation.getId());
143             }
144         }
145         //
146         // Now add all the policies to the DecisionResponse
147         //
148         closestMatches.forEach((thePolicyType, weightMap) ->
149             weightMap.forEach((weight, policies) ->
150                 policies.forEach(policy -> {
151                     LOGGER.info("Policy {}", policy);
152                     decisionResponse.getPolicies().put(policy.getLeft(), policy.getRight());
153                 })
154             )
155         );
156     }
157
158     /**
159      * scanClosestMatchObligation - scans for the obligation specifically holding policy
160      * contents and their details.
161      *
162      * @param closestMatches Map holding the current set of highest weight policy types
163      * @param Obligation Obligation object
164      */
165     protected void scanClosestMatchObligation(
166             Map<String, Map<Integer, List<Pair<String, Map<String, Object>>>>> closestMatches, Obligation obligation) {
167         //
168         // Create our OnapObligation object
169         //
170         OnapObligation onapObligation = new OnapObligation(obligation);
171         //
172         // All 4 *should* be there
173         //
174         if (onapObligation.getPolicyId() == null || onapObligation.getPolicyContent() == null
175                 || onapObligation.getPolicyType() == null || onapObligation.getWeight() == null) {
176             LOGGER.error("Missing an expected attribute in obligation.");
177             return;
178         }
179         //
180         // Save the values
181         //
182         String policyId = onapObligation.getPolicyId();
183         String policyType = onapObligation.getPolicyType();
184         Map<String, Object> policyContent = onapObligation.getPolicyContentAsMap();
185         int policyWeight = onapObligation.getWeight();
186         //
187         // If the Policy Type exists, get the weight map.
188         //
189         Map<Integer, List<Pair<String, Map<String, Object>>>> weightMap = closestMatches.get(policyType);
190         if (weightMap != null) {
191             //
192             // Only need to check first one - as we will ensure there is only one weight
193             //
194             Entry<Integer, List<Pair<String, Map<String, Object>>>> firstEntry =
195                     weightMap.entrySet().iterator().next();
196             if (policyWeight < firstEntry.getKey()) {
197                 //
198                 // Existing policies have a greater weight, so we will not add it
199                 //
200                 LOGGER.info("{} is lesser weight {} than current policies, will not return it", policyWeight,
201                         firstEntry.getKey());
202             } else if (firstEntry.getKey().equals(policyWeight)) {
203                 //
204                 // Same weight - we will add it
205                 //
206                 LOGGER.info("Same weight {}, adding policy", policyWeight);
207                 firstEntry.getValue().add(Pair.of(policyId, policyContent));
208             } else {
209                 //
210                 // The weight is greater, so we need to remove the other policies
211                 // and point to this one.
212                 //
213                 LOGGER.info("New policy has greater weight {}, replacing {}", policyWeight, firstEntry.getKey());
214                 List<Pair<String, Map<String, Object>>> listPolicies = new LinkedList<>();
215                 listPolicies.add(Pair.of(policyId, policyContent));
216                 weightMap.clear();
217                 weightMap.put(policyWeight, listPolicies);
218             }
219         } else {
220             //
221             // Create a new entry
222             //
223             LOGGER.info("New entry {} weight {}", policyType, policyWeight);
224             List<Pair<String, Map<String, Object>>> listPolicies = new LinkedList<>();
225             listPolicies.add(Pair.of(policyId, policyContent));
226             Map<Integer, List<Pair<String, Map<String, Object>>>> newWeightMap = new LinkedHashMap<>();
227             newWeightMap.put(policyWeight, listPolicies);
228             closestMatches.put(policyType, newWeightMap);
229         }
230     }
231
232     @Override
233     public PolicyType convertPolicy(ToscaPolicy toscaPolicy) throws ToscaPolicyConversionException {
234         //
235         // Get the TOSCA Policy Type for this policy
236         //
237         ToscaServiceTemplate toscaPolicyTypeTemplate = this.findPolicyType(toscaPolicy.getTypeIdentifier());
238         //
239         // If we don't have any TOSCA policy types, then we cannot know
240         // which properties are matchable.
241         //
242         if (toscaPolicyTypeTemplate == null) {
243             throw new ToscaPolicyConversionException(
244                     "Cannot retrieve Policy Type definition for policy " + toscaPolicy.getName());
245         }
246         //
247         // Policy name should be at the root
248         //
249         String policyName = toscaPolicy.getMetadata().get(POLICY_ID);
250         //
251         // Set it as the policy ID
252         //
253         PolicyType newPolicyType = new PolicyType();
254         newPolicyType.setPolicyId(policyName);
255         //
256         // Optional description
257         //
258         newPolicyType.setDescription(toscaPolicy.getDescription());
259         //
260         // There should be a metadata section
261         //
262         fillMetadataSection(newPolicyType, toscaPolicy.getMetadata());
263         //
264         // Set the combining rule
265         //
266         newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_FIRST_APPLICABLE.stringValue());
267         //
268         // Generate the TargetType - the policy should not be evaluated
269         // unless all the matchable properties it cares about are matched.
270         //
271         Pair<TargetType, Integer> pairGenerated = generateTargetType(toscaPolicy, toscaPolicyTypeTemplate);
272         newPolicyType.setTarget(pairGenerated.getLeft());
273         //
274         // Now represent the policy as Json
275         //
276         StandardCoder coder = new StandardCoder();
277         String jsonPolicy;
278         try {
279             jsonPolicy = coder.encode(toscaPolicy);
280         } catch (CoderException e) {
281             throw new ToscaPolicyConversionException("Failed to encode policy to json", e);
282         }
283         //
284         // Add it as an obligation
285         //
286         addObligation(newPolicyType, policyName, jsonPolicy, pairGenerated.getRight(), toscaPolicy.getType());
287         //
288         // Now create the Permit Rule.
289         //
290         RuleType rule = new RuleType();
291         rule.setDescription("Default is to PERMIT if the policy matches.");
292         rule.setRuleId(policyName + ":rule");
293         rule.setEffect(EffectType.PERMIT);
294         rule.setTarget(new TargetType());
295         //
296         // The rule contains the Condition which adds logic for
297         // optional policy-type filtering.
298         //
299         rule.setCondition(generateConditionForPolicyType(toscaPolicy.getType()));
300         //
301         // Add the rule to the policy
302         //
303         newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(rule);
304         //
305         // Log output of the policy
306         //
307         try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
308             XACMLPolicyWriter.writePolicyFile(os, newPolicyType);
309             LOGGER.info("{}", os);
310         } catch (IOException e) {
311             LOGGER.error("Failed to create byte array stream", e);
312         }
313         //
314         // Done
315         //
316         return newPolicyType;
317     }
318
319     /**
320      * For generating target type, we scan for matchable properties
321      * and use those to build the policy.
322      *
323      * @param properties Properties section of policy
324      * @param policyTypes Collection of policy Type to find matchable metadata
325      * @return {@code Pair<TargetType, Integer>} Returns a TargetType and a Total Weight of matchables.
326      */
327     protected Pair<TargetType, Integer> generateTargetType(ToscaPolicy policyType,
328             ToscaServiceTemplate policyTemplate) {
329         //
330         // Our return object
331         //
332         TargetType targetType = new TargetType();
333         //
334         // Top-level list of properties
335         //
336         Map<String, Object> properties = policyType.getProperties();
337         //
338         // To start, we know these properties are for this specific Policy Type ID/Version
339         //
340         ToscaPolicyTypeIdentifier propertiesPolicyId = policyType.getTypeIdentifier();
341         //
342         // Scan the property map for matchables
343         //
344         int totalWeight = findMatchablesInProperties(properties, propertiesPolicyId, policyTemplate, targetType);
345         LOGGER.info("Total weight is {}", totalWeight);
346         return Pair.of(targetType, totalWeight);
347     }
348
349     protected int findMatchablesInProperties(Map<String, Object> properties,
350             ToscaPolicyTypeIdentifier propertiesPolicyId,
351             ToscaServiceTemplate policyTemplate,
352             TargetType targetType) {
353         LOGGER.info("findMatchablesInProperties from policy Type {} {}", propertiesPolicyId, properties);
354         //
355         // We better have the policy type definition available from the template
356         //
357         ToscaPolicyType policyType = getToscaPolicyTypeFromTemplate(propertiesPolicyId, policyTemplate);
358         if (policyType == null) {
359             LOGGER.error("Failed to find policy type in template {}", propertiesPolicyId);
360             return 0;
361         }
362         //
363         // Our total weight to return
364         //
365         int totalWeight = 0;
366         for (Entry<String, Object> entrySet : properties.entrySet()) {
367             //
368             // Find the property details
369             //
370             Pair<ToscaProperty, ToscaServiceTemplate> property = findProperty(entrySet.getKey(),
371                     policyType, propertiesPolicyId, policyTemplate);
372             if (property == null) {
373                 continue;
374             }
375             ToscaProperty toscaProperty = property.getLeft();
376             LOGGER.info("Found property {} with type {} schema {}", entrySet.getKey(), toscaProperty.getType(),
377                     (toscaProperty.getEntrySchema() == null ? "null" : toscaProperty.getEntrySchema().getType()));
378             //
379             // Is it matchable?
380             //
381             if (checkIsMatchableProperty(toscaProperty)) {
382                 //
383                 // This will generate the matchables for the property
384                 //
385                 int weight = generateMatchable(targetType, entrySet.getKey(), entrySet.getValue(),
386                         property.getLeft(), property.getRight());
387                 LOGGER.info("Weight is {}", weight);
388                 totalWeight += weight;
389             } else {
390                 //
391                 // Not matchable, but we need to check if this contains list or map of datatypes.
392                 // Those will need to be searched for matchables.
393                 //
394                 if ("list".equals(toscaProperty.getType())) {
395                     int weight = findMatchablesInList(entrySet.getKey(), entrySet.getValue(), toscaProperty,
396                             policyTemplate, targetType);
397                     LOGGER.info("Weight list is {}", weight);
398                     totalWeight += weight;
399                 } else if ("map".equals(toscaProperty.getType())) {
400                     int weight = findMatchablesInMap(entrySet.getKey(), entrySet.getValue(), toscaProperty,
401                             policyTemplate, targetType);
402                     LOGGER.info("Weight map is {}", weight);
403                     totalWeight += weight;
404                 }
405             }
406         }
407         return totalWeight;
408     }
409
410     @SuppressWarnings("unchecked")
411     protected int findMatchablesInList(String listPropertyName, Object listValue, ToscaProperty listProperty,
412             ToscaServiceTemplate listTemplate, TargetType targetType) {
413         //
414         // Don't bother if there is no schema (which should be a problem) or
415         // its a list of primitives
416         //
417         if (listProperty.getEntrySchema() == null) {
418             LOGGER.error("No entry schema for list property {}", listPropertyName);
419             return 0;
420         }
421         //
422         // If they are primitives, then no need to go through them. ??
423         //
424         if (isYamlType(listProperty.getEntrySchema().getType())) {
425             LOGGER.info("list of primitives");
426             return 0;
427         }
428         //
429         // Find the datatype
430         //
431         ToscaDataType listDataType = listTemplate.getDataTypes().get(listProperty.getEntrySchema().getType());
432         if (listDataType == null) {
433             LOGGER.error("Unable to find datatype {}", listProperty.getEntrySchema().getType());
434             return 0;
435         }
436
437         int totalWeight = 0;
438         for (Object datatypeValue : ((Collection<Object>)listValue)) {
439             //
440             // This should be a map - because this is a list of datatypes.
441             //
442             if (! (datatypeValue instanceof Map)) {
443                 LOGGER.error("datatype {} value is not a map {}", listDataType.getName(), datatypeValue.getClass());
444                 continue;
445             }
446             for (Entry<String, Object> entrySet : ((Map<String, Object>)datatypeValue).entrySet()) {
447                 ToscaProperty toscaProperty = listDataType.getProperties().get(entrySet.getKey());
448                 if (toscaProperty == null) {
449                     LOGGER.error("Failed to find datatype {} property {}", listDataType.getName(), entrySet.getKey());
450                     continue;
451                 }
452                 LOGGER.info("Found list property {} with type {} schema {}", entrySet.getKey(), toscaProperty.getType(),
453                         (toscaProperty.getEntrySchema() == null ? "null" : toscaProperty.getEntrySchema().getType()));
454                 //
455                 // Is it matchable?
456                 //
457                 if (checkIsMatchableProperty(toscaProperty)) {
458                     //
459                     // This will generate the matchables for the property
460                     //
461                     int weight = generateMatchable(targetType, entrySet.getKey(), entrySet.getValue(),
462                             toscaProperty, listTemplate);
463                     LOGGER.info("Weight is {}", weight);
464                     totalWeight += weight;
465                 } else {
466                     //
467                     // Not matchable, but we need to check if this contains list or map of datatypes.
468                     // Those will need to be searched for matchables.
469                     //
470                     if ("list".equals(toscaProperty.getType())) {
471                         int weight = findMatchablesInList(entrySet.getKey(), entrySet.getValue(), toscaProperty,
472                                 listTemplate, targetType);
473                         LOGGER.info("Weight list is {}", weight);
474                         totalWeight += weight;
475                     } else if ("map".equals(toscaProperty.getType())) {
476                         int weight = findMatchablesInMap(entrySet.getKey(), entrySet.getValue(), toscaProperty,
477                                 listTemplate, targetType);
478                         LOGGER.info("Weight map is {}", weight);
479                         totalWeight += weight;
480                     }
481                 }
482             }
483         }
484
485         return totalWeight;
486     }
487
488     @SuppressWarnings("unchecked")
489     protected int findMatchablesInMap(String mapPropertyName, Object mapValue, ToscaProperty mapProperty,
490             ToscaServiceTemplate mapTemplate, TargetType targetType) {
491         //
492         // There needs to be a schema.
493         //
494         if (mapProperty.getEntrySchema() == null) {
495             LOGGER.error("No entry schema for map property {}", mapPropertyName);
496             return 0;
497         }
498         //
499         // If they are primitives, then no need to go through them. ??
500         //
501         if (isYamlType(mapProperty.getEntrySchema().getType())) {
502             LOGGER.debug("map property {} is primitives", mapPropertyName);
503             return 0;
504         }
505         //
506         // Find the datatype
507         //
508         ToscaDataType mapDataType = mapTemplate.getDataTypes().get(mapProperty.getEntrySchema().getType());
509         if (mapDataType == null) {
510             LOGGER.error("Unable to find datatype {}", mapProperty.getEntrySchema().getType());
511             return 0;
512         }
513
514         int totalWeight = 0;
515         for (Entry<String, Object> entrySet : ((Map<String, Object>)mapValue).entrySet()) {
516             ToscaProperty toscaProperty = mapDataType.getProperties().get(entrySet.getKey());
517             if (toscaProperty == null) {
518                 LOGGER.error("Failed to find datatype {} property {}", mapDataType.getName(), entrySet.getKey());
519                 continue;
520             }
521             LOGGER.info("Found map property {} with type {} schema {}", entrySet.getKey(), toscaProperty.getType(),
522                     (toscaProperty.getEntrySchema() == null ? "null" : toscaProperty.getEntrySchema().getType()));
523             //
524             // Is it matchable?
525             //
526             if (checkIsMatchableProperty(toscaProperty)) {
527                 //
528                 // This will generate the matchables for the property
529                 //
530                 int weight = generateMatchable(targetType, entrySet.getKey(), entrySet.getValue(),
531                         toscaProperty, mapTemplate);
532                 LOGGER.info("Weight is {}", weight);
533                 totalWeight += weight;
534             } else {
535                 //
536                 // Not matchable, but we need to check if this contains list or map of datatypes.
537                 // Those will need to be searched for matchables.
538                 //
539                 if ("list".equals(toscaProperty.getType())) {
540                     int weight = findMatchablesInList(entrySet.getKey(), entrySet.getValue(), toscaProperty,
541                             mapTemplate, targetType);
542                     LOGGER.info("Weight list is {}", weight);
543                     totalWeight += weight;
544                 } else if ("map".equals(toscaProperty.getType())) {
545                     int weight = findMatchablesInMap(entrySet.getKey(), entrySet.getValue(), toscaProperty,
546                             mapTemplate, targetType);
547                     LOGGER.info("Weight map is {}", weight);
548                     totalWeight += weight;
549                 }
550             }
551         }
552
553         return totalWeight;
554     }
555
556     /**
557      * findMatchableProperty - Iterates through available TOSCA Policy Types and return the
558      * ToscaProperty and template for the property.
559      *
560      * @param propertyName Name of property
561      * @param policyTypes Collection of TOSCA Policy Types to scan
562      * @return ToscaProperty and ToscaServiceTemplate if matchable
563      */
564     protected Pair<ToscaProperty, ToscaServiceTemplate> findProperty(String propertyName,
565             ToscaPolicyType policyType, ToscaPolicyTypeIdentifier propertiesPolicyId,
566             ToscaServiceTemplate policyTemplate) {
567         //
568         // See if the property is defined by the policy template
569         //
570         ToscaProperty toscaProperty = policyType.getProperties().get(propertyName);
571         if (toscaProperty != null) {
572             //
573             // Does it contain the matchable property and if so its set to true?
574             //
575             return Pair.of(toscaProperty, policyTemplate);
576         }
577         LOGGER.debug("property {} is not in policy type {}", propertyName, propertiesPolicyId);
578         //
579         // Check its parent policy types
580         //
581         ToscaPolicyTypeIdentifier parentId = getParentDerivedFrom(propertiesPolicyId, policyTemplate);
582         while (parentId != null) {
583             LOGGER.debug("searching parent policy type {}", parentId);
584             //
585             // Search the existing template (should be there during runtime)
586             //
587             ToscaPolicyType parentPolicyType = getParentPolicyType(parentId, policyTemplate);
588             if (parentPolicyType != null) {
589                 toscaProperty = parentPolicyType.getProperties().get(propertyName);
590                 if (toscaProperty != null) {
591                     return Pair.of(toscaProperty, policyTemplate);
592                 }
593                 //
594                 // Move to the next parent
595                 //
596                 parentId = getParentDerivedFrom(parentId, policyTemplate);
597             } else {
598                 LOGGER.warn("Parent policy type is not found {}", parentId);
599                 //
600                 // Find the parent policy type. During JUnit this may be in a separate
601                 // file. We hope that during runtime the template is complete.
602                 //
603                 ToscaServiceTemplate parentTemplate = findPolicyType(parentId);
604                 if (parentTemplate != null) {
605                     parentPolicyType = getParentPolicyType(parentId, parentTemplate);
606                     if (parentPolicyType != null) {
607                         toscaProperty = parentPolicyType.getProperties().get(propertyName);
608                         if (toscaProperty != null) {
609                             return Pair.of(toscaProperty, parentTemplate);
610                         }
611                     }
612                     //
613                     // Move to the next parent
614                     //
615                     parentId = getParentDerivedFrom(parentId, parentTemplate);
616                 } else {
617                     LOGGER.error("Unable to find/pull parent policy type {}", parentId);
618                     parentId = null;
619                 }
620             }
621         }
622         LOGGER.warn("Property {} is NOT found in any template", propertyName);
623         return null;
624     }
625
626     private ToscaPolicyType getToscaPolicyTypeFromTemplate(ToscaPolicyTypeIdentifier propertiesPolicyId,
627             ToscaServiceTemplate policyTemplate) {
628         for (Entry<String, ToscaPolicyType> entry : policyTemplate.getPolicyTypes().entrySet()) {
629             if (propertiesPolicyId.getName().equals(entry.getKey())
630                     && propertiesPolicyId.getVersion().equals(entry.getValue().getVersion())) {
631                 return entry.getValue();
632             }
633         }
634         return null;
635     }
636
637     private boolean isYamlType(String type) {
638         return "string".equalsIgnoreCase(type) || "integer".equalsIgnoreCase(type) || "float".equalsIgnoreCase(type)
639                 || "boolean".equalsIgnoreCase(type) || "timestamp".equalsIgnoreCase(type);
640     }
641
642     /**
643      * checkIsMatchableProperty - checks the property metadata to see if matchable exists.
644      *
645      * @param toscaProperty ToscaProperty
646      * @return true if matchable
647      */
648     protected boolean checkIsMatchableProperty(ToscaProperty toscaProperty) {
649         if (toscaProperty.getMetadata() == null) {
650             return false;
651         }
652         for (Entry<String, String> entrySet : toscaProperty.getMetadata().entrySet()) {
653             if ("matchable".equals(entrySet.getKey()) && "true".equals(entrySet.getValue())) {
654                 LOGGER.debug("found matchable of type {}", toscaProperty.getType());
655                 return true;
656             }
657         }
658         return false;
659     }
660
661     /**
662      * generateMatchable - Given the object, generates list of MatchType objects and add them
663      * to the TargetType object. Returns a weight which is the number of AnyOf's generated. The
664      * weight can be used to further filter the results for "closest match".
665      *
666      * @param targetType TargetType object to add matches to
667      * @param key Property key
668      * @param value Object is the value - which can be a Collection or single Object
669      * @param toscaProperty The property that was found
670      * @param toscaServiceTemplate The template from which the property was found
671      * @return int Weight of the match.
672      */
673     protected int generateMatchable(TargetType targetType, String key, Object value, ToscaProperty toscaProperty,
674             ToscaServiceTemplate toscaServiceTemplate) {
675         int weight = 0;
676         if (value instanceof Collection) {
677             //
678             // Further determine how we treat this collection. We will need the schema
679             // if it is not available then we have to bail.
680             //
681             if (toscaProperty.getEntrySchema() == null) {
682                 LOGGER.error("No schema for property {} of type {}", key, toscaProperty.getType());
683             }
684             if ("list".equals(toscaProperty.getType())) {
685                 return generateMatchableList(targetType, key, value, toscaProperty, toscaServiceTemplate);
686             }
687             if ("map".equals(toscaProperty.getType())) {
688                 return generateMatchableMap(targetType, key, value, toscaProperty, toscaServiceTemplate);
689             }
690         } else {
691             AnyOfType anyOf = generateMatches(Arrays.asList(value),
692                     new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + key));
693             if (! anyOf.getAllOf().isEmpty()) {
694                 targetType.getAnyOf().add(anyOf);
695                 weight = 1;
696             }
697         }
698         return weight;
699     }
700
701     @SuppressWarnings("unchecked")
702     protected int generateMatchableList(TargetType targetType, String key, Object value, ToscaProperty toscaProperty,
703             ToscaServiceTemplate toscaServiceTemplate) {
704         int weight = 0;
705         if (isYamlType(toscaProperty.getEntrySchema().getType())) {
706             AnyOfType anyOf = generateMatches((Collection<Object>) value,
707                     new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + key));
708             if (! anyOf.getAllOf().isEmpty()) {
709                 targetType.getAnyOf().add(anyOf);
710                 weight = 1;
711             }
712         } else {
713             LOGGER.debug("PLD use datatype for list?");
714         }
715         return weight;
716     }
717
718     @SuppressWarnings("unchecked")
719     protected int generateMatchableMap(TargetType targetType, String key, Object value, ToscaProperty toscaProperty,
720             ToscaServiceTemplate toscaServiceTemplate) {
721         int weight = 0;
722         if (isYamlType(toscaProperty.getEntrySchema().getType())) {
723             //
724             // PLD TODO - this won't work
725             //
726             AnyOfType anyOf = generateMatches((Collection<Object>) value,
727                     new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + key));
728             if (! anyOf.getAllOf().isEmpty()) {
729                 targetType.getAnyOf().add(anyOf);
730                 weight = 1;
731             }
732         } else {
733             LOGGER.debug("PLD use datatype for map?");
734         }
735         return weight;
736     }
737
738     /**
739      * generateMatches - Goes through the collection of objects, creates a MatchType object
740      * for each object and associates it with the given attribute Id. Returns the AnyOfType
741      * object that contains all the generated MatchType objects.
742      *
743      * @param matchables Collection of object to generate MatchType from
744      * @param attributeId Given attribute Id for each MatchType
745      * @return AnyOfType object
746      */
747     protected AnyOfType generateMatches(Collection<Object> matchables, Identifier attributeId) {
748         //
749         // This is our outer AnyOf - which is an OR
750         //
751         AnyOfType anyOf = new AnyOfType();
752         for (Object matchable : matchables) {
753             //
754             // Default to string
755             //
756             Identifier idFunction = XACML3.ID_FUNCTION_STRING_EQUAL;
757             Identifier idDatatype = XACML3.ID_DATATYPE_STRING;
758             //
759             // See if we are another datatype
760             //
761             // We should add datetime support. But to do that we need
762             // probably more metadata to describe how that would be translated.
763             //
764             if (matchable instanceof Integer) {
765                 idFunction = XACML3.ID_FUNCTION_INTEGER_EQUAL;
766                 idDatatype = XACML3.ID_DATATYPE_INTEGER;
767             } else if (matchable instanceof Double) {
768                 idFunction = XACML3.ID_FUNCTION_DOUBLE_EQUAL;
769                 idDatatype = XACML3.ID_DATATYPE_DOUBLE;
770             } else if (matchable instanceof Boolean) {
771                 idFunction = XACML3.ID_FUNCTION_BOOLEAN_EQUAL;
772                 idDatatype = XACML3.ID_DATATYPE_BOOLEAN;
773             }
774             //
775             // Create a match for this
776             //
777             MatchType match = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
778                     idFunction,
779                     matchable.toString(),
780                     idDatatype,
781                     attributeId,
782                     XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
783             //
784             // Now create an anyOf (OR)
785             //
786             anyOf.getAllOf().add(ToscaPolicyTranslatorUtils.buildAllOf(match));
787         }
788         return anyOf;
789     }
790
791     private ToscaPolicyTypeIdentifier getParentDerivedFrom(ToscaPolicyTypeIdentifier policyTypeId,
792             ToscaServiceTemplate template) {
793         for (Entry<String, ToscaPolicyType> entrySet : template.getPolicyTypes().entrySet()) {
794             ToscaPolicyType policyType = entrySet.getValue();
795             if (entrySet.getKey().equals(policyTypeId.getName())
796                     && policyType.getVersion().equals(policyTypeId.getVersion())
797                     && ! "tosca.policies.Root".equals(policyType.getDerivedFrom())) {
798                 return new ToscaPolicyTypeIdentifier(policyType.getDerivedFrom(), "1.0.0");
799             }
800         }
801
802         return null;
803     }
804
805     private ToscaPolicyType getParentPolicyType(ToscaPolicyTypeIdentifier policyTypeId, ToscaServiceTemplate template) {
806         for (Entry<String, ToscaPolicyType> entrySet : template.getPolicyTypes().entrySet()) {
807             ToscaPolicyType policyType = entrySet.getValue();
808             if (entrySet.getKey().equals(policyTypeId.getName())
809                     && policyType.getVersion().equals(policyTypeId.getVersion())) {
810                 return policyType;
811             }
812         }
813         return null;
814     }
815
816     /**
817      * findPolicyType - given the ToscaPolicyTypeIdentifier, finds it in memory, or
818      * then tries to find it either locally on disk or pull it from the Policy
819      * Lifecycle API the given TOSCA Policy Type.
820      *
821      * @param policyTypeId ToscaPolicyTypeIdentifier to find
822      * @return ToscaPolicyType object. Can be null if failure.
823      */
824     private ToscaServiceTemplate findPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
825         //
826         // Is it loaded in memory?
827         //
828         ToscaServiceTemplate policyTemplate = this.matchablePolicyTypes.get(policyTypeId);
829         if (policyTemplate == null)  {
830             //
831             // Load the policy
832             //
833             policyTemplate = this.loadPolicyType(policyTypeId);
834             //
835             // Save it
836             //
837             if (policyTemplate != null) {
838                 this.matchablePolicyTypes.put(policyTypeId, policyTemplate);
839             }
840         }
841         //
842         // Yep return it
843         //
844         return policyTemplate;
845     }
846
847     /**
848      * loadPolicyType - Tries to load the given ToscaPolicyTypeIdentifier from local
849      * storage. If it does not exist, will then attempt to pull from Policy Lifecycle
850      * API.
851      *
852      * @param policyTypeId ToscaPolicyTypeIdentifier input
853      * @return ToscaPolicyType object. Null if failure.
854      */
855     private ToscaServiceTemplate loadPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
856         //
857         // Construct what the file name should be
858         //
859         Path policyTypePath = this.constructLocalFilePath(policyTypeId);
860         //
861         // See if it exists
862         //
863         byte[] bytes;
864         try {
865             //
866             // If it exists locally, read the bytes in
867             //
868             bytes = Files.readAllBytes(policyTypePath);
869         } catch (IOException e) {
870             //
871             // Does not exist locally, so let's GET it from the policy api
872             //
873             LOGGER.error("PolicyType not found in data area yet {}", policyTypePath, e);
874             //
875             // So let's pull it from API REST call and save it locally
876             //
877             return this.pullPolicyType(policyTypeId, policyTypePath);
878         }
879         //
880         // Success - we have read locally the policy type. Now bring it into our
881         // return object.
882         //
883         LOGGER.info("Read in local policy type {}", policyTypePath.toAbsolutePath());
884         try {
885             return standardYamlCoder.decode(new String(bytes, StandardCharsets.UTF_8),
886                     ToscaServiceTemplate.class);
887         } catch (CoderException e) {
888             LOGGER.error("Failed to decode tosca template for {}", policyTypePath, e);
889         }
890         //
891         // Hopefully we never get here
892         //
893         LOGGER.error("Failed to find/load policy type {}", policyTypeId);
894         return null;
895     }
896
897     /**
898      * pullPolicyType - pulls the given ToscaPolicyTypeIdentifier from the Policy Lifecycle API.
899      * If successful, will store it locally given the policyTypePath.
900      *
901      * @param policyTypeId ToscaPolicyTypeIdentifier
902      * @param policyTypePath Path object to store locally
903      * @return ToscaPolicyType object. Null if failure.
904      */
905     private synchronized ToscaServiceTemplate pullPolicyType(ToscaPolicyTypeIdentifier policyTypeId,
906             Path policyTypePath) {
907         //
908         // This is what we return
909         //
910         ToscaServiceTemplate policyTemplate = null;
911         try {
912             PolicyApiCaller api = new PolicyApiCaller(this.apiRestParameters);
913
914             policyTemplate = api.getPolicyType(policyTypeId);
915         } catch (PolicyApiException e) {
916             LOGGER.error("Failed to make API call", e);
917             LOGGER.error("parameters: {} ", this.apiRestParameters);
918             return null;
919         }
920         LOGGER.info("Successfully pulled {}", policyTypeId);
921         //
922         // Store it locally
923         //
924         try {
925             standardYamlCoder.encode(policyTypePath.toFile(), policyTemplate);
926         } catch (CoderException e) {
927             LOGGER.error("Failed to store {} locally to {}", policyTypeId, policyTypePath, e);
928         }
929         //
930         // Done return the policy type
931         //
932         return policyTemplate;
933     }
934
935     /**
936      * constructLocalFilePath - common method to ensure the name of the local file for the
937      * policy type is the same.
938      *
939      * @param policyTypeId ToscaPolicyTypeIdentifier
940      * @return Path object
941      */
942     private Path constructLocalFilePath(ToscaPolicyTypeIdentifier policyTypeId) {
943         return Paths.get(this.pathForData.toAbsolutePath().toString(), policyTypeId.getName() + "-"
944                 + policyTypeId.getVersion() + ".yaml");
945     }
946 }