2  * ============LICENSE_START=======================================================
 
   4  * ================================================================================
 
   5  * Copyright (C) 2019-2021 AT&T Intellectual Property. All rights reserved.
 
   6  * Modifications Copyright (C) 2021 Nordix Foundation.
 
   7  * ================================================================================
 
   8  * Licensed under the Apache License, Version 2.0 (the "License");
 
   9  * you may not use this file except in compliance with the License.
 
  10  * You may obtain a copy of the License at
 
  12  *      http://www.apache.org/licenses/LICENSE-2.0
 
  14  * Unless required by applicable law or agreed to in writing, software
 
  15  * distributed under the License is distributed on an "AS IS" BASIS,
 
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
  17  * See the License for the specific language governing permissions and
 
  18  * limitations under the License.
 
  20  * SPDX-License-Identifier: Apache-2.0
 
  21  * ============LICENSE_END=========================================================
 
  24 package org.onap.policy.pdp.xacml.application.common.std;
 
  26 import com.att.research.xacml.api.Advice;
 
  27 import com.att.research.xacml.api.Identifier;
 
  28 import com.att.research.xacml.api.Obligation;
 
  29 import com.att.research.xacml.api.Request;
 
  30 import com.att.research.xacml.api.XACML3;
 
  31 import com.att.research.xacml.std.IdentifierImpl;
 
  32 import com.att.research.xacml.util.XACMLPolicyWriter;
 
  33 import java.io.ByteArrayOutputStream;
 
  34 import java.io.IOException;
 
  35 import java.nio.charset.StandardCharsets;
 
  36 import java.nio.file.Files;
 
  37 import java.nio.file.Path;
 
  38 import java.nio.file.Paths;
 
  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;
 
  45 import java.util.Map.Entry;
 
  47 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AllOfType;
 
  48 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AnyOfType;
 
  49 import oasis.names.tc.xacml._3_0.core.schema.wd_17.EffectType;
 
  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.event.comm.bus.internal.BusTopicParams;
 
  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.ToscaConceptIdentifier;
 
  61 import org.onap.policy.models.tosca.authorative.concepts.ToscaDataType;
 
  62 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
 
  63 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType;
 
  64 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
 
  65 import org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate;
 
  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.onap.policy.pdp.xacml.application.common.matchable.MatchableCallback;
 
  74 import org.onap.policy.pdp.xacml.application.common.matchable.MatchablePolicyType;
 
  75 import org.onap.policy.pdp.xacml.application.common.matchable.MatchableProperty;
 
  76 import org.slf4j.Logger;
 
  77 import org.slf4j.LoggerFactory;
 
  80  * This standard matchable translator uses Policy Types that contain "matchable" field in order
 
  81  * to translate policies.
 
  83  * @author pameladragosh
 
  86 public class StdMatchableTranslator  extends StdBaseTranslator implements MatchableCallback {
 
  88     private static final Logger LOGGER = LoggerFactory.getLogger(StdMatchableTranslator.class);
 
  89     private static final StandardYamlCoder standardYamlCoder = new StandardYamlCoder();
 
  91     private final Map<ToscaConceptIdentifier, ToscaServiceTemplate> matchablePolicyTypes = new HashMap<>();
 
  92     private final Map<ToscaConceptIdentifier, MatchablePolicyType> matchableCache = new HashMap<>();
 
  95     private BusTopicParams apiRestParameters;
 
  97     private Path pathForData;
 
  99     public StdMatchableTranslator() {
 
 104     public Request convertRequest(DecisionRequest request) throws ToscaPolicyConversionException {
 
 105         LOGGER.info("Converting Request {}", request);
 
 107             return StdMatchablePolicyRequest.createInstance(request);
 
 108         } catch (XacmlApplicationException e) {
 
 109             throw new ToscaPolicyConversionException("Failed to convert DecisionRequest", e);
 
 114      * scanObligations - scans the list of obligations and make appropriate method calls to process
 
 117      * @param obligations Collection of obligation objects
 
 118      * @param decisionResponse DecisionResponse object used to store any results from obligations.
 
 121     protected void scanObligations(Collection<Obligation> obligations, DecisionResponse decisionResponse) {
 
 123         // Implementing a crude "closest match" on the results, which means we will strip out
 
 124         // any policies that has the lower weight than any of the others.
 
 126         // Most likely these are "default" policies with a weight of zero, but not always.
 
 128         // It is possible to have multiple policies with an equal weight, that is desired.
 
 130         // So we need to track each policy type separately and the weights for each policy.
 
 132         // policy-type -> weight -> List({policy-id, policy-content}, {policy-id, policy-content})
 
 134         Map<String, Map<Integer, List<Pair<String, Map<String, Object>>>>> closestMatches = new LinkedHashMap<>();
 
 136         // Now scan the list of obligations
 
 138         for (Obligation obligation : obligations) {
 
 139             Identifier obligationId = obligation.getId();
 
 140             LOGGER.info("Obligation: {}", obligationId);
 
 141             if (ToscaDictionary.ID_OBLIGATION_REST_BODY.equals(obligationId)) {
 
 142                 scanClosestMatchObligation(closestMatches, obligation);
 
 144                 LOGGER.warn("Unsupported Obligation Id {}", obligation.getId());
 
 148         // Now add all the policies to the DecisionResponse
 
 150         closestMatches.forEach((thePolicyType, weightMap) ->
 
 151             weightMap.forEach((weight, policies) ->
 
 152                 policies.forEach(policy -> {
 
 153                     LOGGER.info("Policy {}", policy);
 
 154                     decisionResponse.getPolicies().put(policy.getLeft(), policy.getRight());
 
 161     protected void scanAdvice(Collection<Advice> advice, DecisionResponse decisionResponse) {
 
 162         LOGGER.warn("scanAdvice not supported by {}", this.getClass());
 
 166      * scanClosestMatchObligation - scans for the obligation specifically holding policy
 
 167      * contents and their details.
 
 169      * @param closestMatches Map holding the current set of highest weight policy types
 
 170      * @param Obligation Obligation object
 
 172     protected void scanClosestMatchObligation(
 
 173             Map<String, Map<Integer, List<Pair<String, Map<String, Object>>>>> closestMatches, Obligation obligation) {
 
 175         // Create our OnapObligation object
 
 177         var onapObligation = new OnapObligation(obligation);
 
 179         // All 4 *should* be there
 
 181         if (onapObligation.getPolicyId() == null || onapObligation.getPolicyContent() == null
 
 182                 || onapObligation.getPolicyType() == null || onapObligation.getWeight() == null) {
 
 183             LOGGER.error("Missing an expected attribute in obligation.");
 
 189         String policyId = onapObligation.getPolicyId();
 
 190         String policyType = onapObligation.getPolicyType();
 
 191         Map<String, Object> policyContent = onapObligation.getPolicyContentAsMap();
 
 192         int policyWeight = onapObligation.getWeight();
 
 194         // If the Policy Type exists, get the weight map.
 
 196         Map<Integer, List<Pair<String, Map<String, Object>>>> weightMap = closestMatches.get(policyType);
 
 197         if (weightMap != null) {
 
 199             // Only need to check first one - as we will ensure there is only one weight
 
 201             Entry<Integer, List<Pair<String, Map<String, Object>>>> firstEntry =
 
 202                     weightMap.entrySet().iterator().next();
 
 203             if (policyWeight < firstEntry.getKey()) {
 
 205                 // Existing policies have a greater weight, so we will not add it
 
 207                 LOGGER.info("{} is lesser weight {} than current policies, will not return it", policyWeight,
 
 208                         firstEntry.getKey());
 
 209             } else if (firstEntry.getKey().equals(policyWeight)) {
 
 211                 // Same weight - we will add it
 
 213                 LOGGER.info("Same weight {}, adding policy", policyWeight);
 
 214                 firstEntry.getValue().add(Pair.of(policyId, policyContent));
 
 217                 // The weight is greater, so we need to remove the other policies
 
 218                 // and point to this one.
 
 220                 LOGGER.info("New policy has greater weight {}, replacing {}", policyWeight, firstEntry.getKey());
 
 221                 List<Pair<String, Map<String, Object>>> listPolicies = new LinkedList<>();
 
 222                 listPolicies.add(Pair.of(policyId, policyContent));
 
 224                 weightMap.put(policyWeight, listPolicies);
 
 228             // Create a new entry
 
 230             LOGGER.info("New entry {} weight {}", policyType, policyWeight);
 
 231             List<Pair<String, Map<String, Object>>> listPolicies = new LinkedList<>();
 
 232             listPolicies.add(Pair.of(policyId, policyContent));
 
 233             Map<Integer, List<Pair<String, Map<String, Object>>>> newWeightMap = new LinkedHashMap<>();
 
 234             newWeightMap.put(policyWeight, listPolicies);
 
 235             closestMatches.put(policyType, newWeightMap);
 
 240     public Object convertPolicy(ToscaPolicy toscaPolicy) throws ToscaPolicyConversionException {
 
 242         // Get the TOSCA Policy Type for this policy
 
 244         ToscaServiceTemplate toscaPolicyTypeTemplate = this.findPolicyType(toscaPolicy.getTypeIdentifier());
 
 246         // If we don't have any TOSCA policy types, then we cannot know
 
 247         // which properties are matchable.
 
 249         if (toscaPolicyTypeTemplate == null) {
 
 250             throw new ToscaPolicyConversionException(
 
 251                     "Cannot retrieve Policy Type definition for policy " + toscaPolicy.getName());
 
 254         // Policy name should be at the root
 
 256         String policyName = toscaPolicy.getMetadata().get(POLICY_ID);
 
 258         // Set it as the policy ID
 
 260         var newPolicyType = new PolicyType();
 
 261         newPolicyType.setPolicyId(policyName);
 
 263         // Optional description
 
 265         newPolicyType.setDescription(toscaPolicy.getDescription());
 
 267         // There should be a metadata section
 
 269         fillMetadataSection(newPolicyType, toscaPolicy.getMetadata());
 
 271         // Set the combining rule
 
 273         newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_FIRST_APPLICABLE.stringValue());
 
 275         // Generate the TargetType - the policy should not be evaluated
 
 276         // unless all the matchable properties it cares about are matched.
 
 278         Pair<TargetType, Integer> pairGenerated = generateTargetType(toscaPolicy, toscaPolicyTypeTemplate);
 
 279         newPolicyType.setTarget(pairGenerated.getLeft());
 
 281         // Now represent the policy as Json
 
 283         var coder = new StandardCoder();
 
 286             jsonPolicy = coder.encode(toscaPolicy);
 
 287         } catch (CoderException e) {
 
 288             throw new ToscaPolicyConversionException("Failed to encode policy to json", e);
 
 291         // Add it as an obligation
 
 293         addObligation(newPolicyType, policyName, jsonPolicy, pairGenerated.getRight(), toscaPolicy.getType());
 
 295         // Now create the Permit Rule.
 
 297         var rule = new RuleType();
 
 298         rule.setDescription("Default is to PERMIT if the policy matches.");
 
 299         rule.setRuleId(policyName + ":rule");
 
 300         rule.setEffect(EffectType.PERMIT);
 
 301         rule.setTarget(new TargetType());
 
 303         // The rule contains the Condition which adds logic for
 
 304         // optional policy-type filtering.
 
 306         rule.setCondition(generateConditionForPolicyType(toscaPolicy.getType()));
 
 308         // Add the rule to the policy
 
 310         newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(rule);
 
 312         // Log output of the policy
 
 314         try (var os = new ByteArrayOutputStream()) {
 
 315             XACMLPolicyWriter.writePolicyFile(os, newPolicyType);
 
 316             LOGGER.info("{}", os);
 
 317         } catch (IOException e) {
 
 318             LOGGER.error("Failed to create byte array stream", e);
 
 323         return newPolicyType;
 
 327     public ToscaPolicyType retrievePolicyType(String derivedFrom) {
 
 328         ToscaServiceTemplate template = this.findPolicyType(new ToscaConceptIdentifier(derivedFrom, "1.0.0"));
 
 329         if (template == null) {
 
 330             LOGGER.error("Could not retrieve Policy Type {}", derivedFrom);
 
 333         return template.getPolicyTypes().get(derivedFrom);
 
 337     public ToscaDataType retrieveDataType(String datatype) {
 
 339         // Our outer class is not storing the current template being scanned
 
 341         LOGGER.error("this retrieveDataType should not be called.");
 
 345     private class MyMatchableCallback implements MatchableCallback {
 
 346         private StdMatchableTranslator translator;
 
 347         private ToscaServiceTemplate template;
 
 349         public MyMatchableCallback(StdMatchableTranslator translator, ToscaServiceTemplate template) {
 
 350             this.translator = translator;
 
 351             this.template = template;
 
 355         public ToscaPolicyType retrievePolicyType(String derivedFrom) {
 
 356             ToscaPolicyType policyType = this.template.getPolicyTypes().get(derivedFrom);
 
 357             if (policyType != null) {
 
 360             return translator.retrievePolicyType(derivedFrom);
 
 364         public ToscaDataType retrieveDataType(String datatype) {
 
 365             return this.template.getDataTypes().get(datatype);
 
 371      * For generating target type, we scan for matchable properties
 
 372      * and use those to build the policy.
 
 374      * @param properties Properties section of policy
 
 375      * @param policyTypes Collection of policy Type to find matchable metadata
 
 376      * @return {@code Pair<TargetType, Integer>} Returns a TargetType and a Total Weight of matchables.
 
 378     protected Pair<TargetType, Integer> generateTargetType(ToscaPolicy policy, ToscaServiceTemplate template) {
 
 382         var target = new TargetType();
 
 384         // See if we have a matchable in the cache already
 
 386         var matchablePolicyType = matchableCache.get(policy.getTypeIdentifier());
 
 388         // If not found, create one
 
 390         if (matchablePolicyType == null) {
 
 394             var myCallback = new MyMatchableCallback(this, template);
 
 396             // Create the matchable
 
 398             matchablePolicyType = new MatchablePolicyType(
 
 399                     template.getPolicyTypes().get(policy.getType()), myCallback);
 
 403             matchableCache.put(policy.getTypeIdentifier(), matchablePolicyType);
 
 406         // Fill in the target type with potential matchables
 
 409             fillTargetTypeWithMatchables(target, matchablePolicyType, policy.getProperties());
 
 410         } catch (ToscaPolicyConversionException e) {
 
 411             LOGGER.error("Could not generate target type", e);
 
 414         // There may be a case for default policies there is no weight - need to clean
 
 415         // up the target then else PDP will report bad policy missing AnyOf
 
 417         int weight = calculateWeight(target);
 
 418         LOGGER.debug("Weight is {} for policy {}", weight, policy.getName());
 
 420         // Assume the number of AllOf's is the weight for now
 
 422         return Pair.of(target, weight);
 
 425     @SuppressWarnings("unchecked")
 
 426     protected void fillTargetTypeWithMatchables(TargetType target, MatchablePolicyType matchablePolicyType,
 
 427             Map<String, Object> properties) throws ToscaPolicyConversionException {
 
 428         for (Entry<String, Object> entrySet : properties.entrySet()) {
 
 429             String propertyName = entrySet.getKey();
 
 430             Object propertyValue = entrySet.getValue();
 
 431             MatchableProperty matchable = matchablePolicyType.get(propertyName);
 
 432             if (matchable != null) {
 
 434                 // Construct attribute id
 
 436                 Identifier id = new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + propertyName);
 
 438                 // Depending on what type it is, add it into the target
 
 440                 ToscaPolicyTranslatorUtils.buildAndAppendTarget(target,
 
 441                         matchable.getType().generate(propertyValue, id));
 
 446             // Here is the special case where we look for a Collection of values that may
 
 447             // contain potential matchables
 
 449             if (propertyValue instanceof List) {
 
 450                 for (Object listValue : ((List<?>) propertyValue)) {
 
 451                     if (listValue instanceof Map) {
 
 452                         fillTargetTypeWithMatchables(target, matchablePolicyType, (Map<String, Object>) listValue);
 
 455             } else if (propertyValue instanceof Map) {
 
 456                 fillTargetTypeWithMatchables(target, matchablePolicyType, (Map<String, Object>) propertyValue);
 
 461     protected int calculateWeight(TargetType target) {
 
 463         for (AnyOfType anyOf : target.getAnyOf()) {
 
 464             for (AllOfType allOf : anyOf.getAllOf()) {
 
 465                 weight += allOf.getMatch().size();
 
 473      * findPolicyType - given the ToscaConceptIdentifier, finds it in memory, or
 
 474      * then tries to find it either locally on disk or pull it from the Policy
 
 475      * Lifecycle API the given TOSCA Policy Type.
 
 477      * @param policyTypeId ToscaConceptIdentifier to find
 
 478      * @return ToscaPolicyType object. Can be null if failure.
 
 480     private ToscaServiceTemplate findPolicyType(ToscaConceptIdentifier policyTypeId) {
 
 482         // Is it loaded in memory?
 
 484         ToscaServiceTemplate policyTemplate = this.matchablePolicyTypes.get(policyTypeId);
 
 485         if (policyTemplate == null)  {
 
 489             policyTemplate = this.loadPolicyType(policyTypeId);
 
 493             if (policyTemplate != null) {
 
 494                 this.matchablePolicyTypes.put(policyTypeId, policyTemplate);
 
 500         return policyTemplate;
 
 504      * loadPolicyType - Tries to load the given ToscaConceptIdentifier from local
 
 505      * storage. If it does not exist, will then attempt to pull from Policy Lifecycle
 
 508      * @param policyTypeId ToscaConceptIdentifier input
 
 509      * @return ToscaPolicyType object. Null if failure.
 
 511     private ToscaServiceTemplate loadPolicyType(ToscaConceptIdentifier policyTypeId) {
 
 513         // Construct what the file name should be
 
 515         var policyTypePath = this.constructLocalFilePath(policyTypeId);
 
 522             // If it exists locally, read the bytes in
 
 524             bytes = Files.readAllBytes(policyTypePath);
 
 525         } catch (IOException e) {
 
 527             // Does not exist locally, so let's GET it from the policy api
 
 529             LOGGER.error("PolicyType not found in data area yet {}", policyTypePath, e);
 
 531             // So let's pull it from API REST call and save it locally
 
 533             return this.pullPolicyType(policyTypeId, policyTypePath);
 
 536         // Success - we have read locally the policy type. Now bring it into our
 
 539         LOGGER.info("Read in local policy type {}", policyTypePath.toAbsolutePath());
 
 542             // Decode the template
 
 544             ToscaServiceTemplate template = standardYamlCoder.decode(new String(bytes, StandardCharsets.UTF_8),
 
 545                     ToscaServiceTemplate.class);
 
 547             // Ensure all the fields are setup correctly
 
 549             var jtst = new JpaToscaServiceTemplate();
 
 550             jtst.fromAuthorative(template);
 
 551             return jtst.toAuthorative();
 
 552         } catch (CoderException e) {
 
 553             LOGGER.error("Failed to decode tosca template for {}", policyTypePath, e);
 
 556         // Hopefully we never get here
 
 558         LOGGER.error("Failed to find/load policy type {}", policyTypeId);
 
 563      * pullPolicyType - pulls the given ToscaConceptIdentifier from the Policy Lifecycle API.
 
 564      * If successful, will store it locally given the policyTypePath.
 
 566      * @param policyTypeId ToscaConceptIdentifier
 
 567      * @param policyTypePath Path object to store locally
 
 568      * @return ToscaPolicyType object. Null if failure.
 
 570     private synchronized ToscaServiceTemplate pullPolicyType(ToscaConceptIdentifier policyTypeId,
 
 571             Path policyTypePath) {
 
 573         // This is what we return
 
 575         ToscaServiceTemplate policyTemplate = null;
 
 577             var api = new PolicyApiCaller(this.apiRestParameters);
 
 579             policyTemplate = api.getPolicyType(policyTypeId);
 
 580         } catch (PolicyApiException e) {
 
 581             LOGGER.error("Failed to make API call", e);
 
 584         LOGGER.info("Successfully pulled {}", policyTypeId);
 
 589             standardYamlCoder.encode(policyTypePath.toFile(), policyTemplate);
 
 590         } catch (CoderException e) {
 
 591             LOGGER.error("Failed to store {} locally to {}", policyTypeId, policyTypePath, e);
 
 594         // Done return the policy type
 
 596         return policyTemplate;
 
 600      * constructLocalFilePath - common method to ensure the name of the local file for the
 
 601      * policy type is the same.
 
 603      * @param policyTypeId ToscaConceptIdentifier
 
 604      * @return Path object
 
 606     private Path constructLocalFilePath(ToscaConceptIdentifier policyTypeId) {
 
 607         return Paths.get(this.pathForData.toAbsolutePath().toString(), policyTypeId.getName() + "-"
 
 608                 + policyTypeId.getVersion() + ".yaml");