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