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