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