2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 * SPDX-License-Identifier: Apache-2.0
20 * ============LICENSE_END=========================================================
23 package org.onap.policy.pdp.xacml.application.common.std;
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.Collection;
39 import java.util.HashMap;
40 import java.util.LinkedHashMap;
41 import java.util.LinkedList;
42 import java.util.List;
44 import java.util.Map.Entry;
46 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AllOfType;
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.PolicyType;
50 import oasis.names.tc.xacml._3_0.core.schema.wd_17.RuleType;
51 import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType;
52 import org.apache.commons.lang3.tuple.Pair;
53 import org.onap.policy.common.endpoints.parameters.RestServerParameters;
54 import org.onap.policy.common.utils.coder.CoderException;
55 import org.onap.policy.common.utils.coder.StandardCoder;
56 import org.onap.policy.common.utils.coder.StandardYamlCoder;
57 import org.onap.policy.models.decisions.concepts.DecisionRequest;
58 import org.onap.policy.models.decisions.concepts.DecisionResponse;
59 import org.onap.policy.models.tosca.authorative.concepts.ToscaDataType;
60 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
61 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType;
62 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
63 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
64 import org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate;
65 import org.onap.policy.pdp.xacml.application.common.OnapObligation;
66 import org.onap.policy.pdp.xacml.application.common.PolicyApiCaller;
67 import org.onap.policy.pdp.xacml.application.common.PolicyApiException;
68 import org.onap.policy.pdp.xacml.application.common.ToscaDictionary;
69 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConversionException;
70 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslatorUtils;
71 import org.onap.policy.pdp.xacml.application.common.XacmlApplicationException;
72 import org.onap.policy.pdp.xacml.application.common.matchable.MatchableCallback;
73 import org.onap.policy.pdp.xacml.application.common.matchable.MatchablePolicyType;
74 import org.onap.policy.pdp.xacml.application.common.matchable.MatchableProperty;
75 import org.slf4j.Logger;
76 import org.slf4j.LoggerFactory;
79 * This standard matchable translator uses Policy Types that contain "matchable" field in order
80 * to translate policies.
82 * @author pameladragosh
85 public class StdMatchableTranslator extends StdBaseTranslator implements MatchableCallback {
87 private static final Logger LOGGER = LoggerFactory.getLogger(StdMatchableTranslator.class);
88 private static final StandardYamlCoder standardYamlCoder = new StandardYamlCoder();
90 private final Map<ToscaPolicyTypeIdentifier, ToscaServiceTemplate> matchablePolicyTypes = new HashMap<>();
91 private final Map<ToscaPolicyTypeIdentifier, MatchablePolicyType> matchableCache = new HashMap<>();
94 private RestServerParameters apiRestParameters;
96 private Path pathForData;
98 public StdMatchableTranslator() {
103 public Request convertRequest(DecisionRequest request) throws ToscaPolicyConversionException {
104 LOGGER.info("Converting Request {}", request);
106 return StdMatchablePolicyRequest.createInstance(request);
107 } catch (XacmlApplicationException e) {
108 throw new ToscaPolicyConversionException("Failed to convert DecisionRequest", e);
113 * scanObligations - scans the list of obligations and make appropriate method calls to process
116 * @param obligations Collection of obligation objects
117 * @param decisionResponse DecisionResponse object used to store any results from obligations.
120 protected void scanObligations(Collection<Obligation> obligations, DecisionResponse decisionResponse) {
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.
125 // Most likely these are "default" policies with a weight of zero, but not always.
127 // It is possible to have multiple policies with an equal weight, that is desired.
129 // So we need to track each policy type separately and the weights for each policy.
131 // policy-type -> weight -> List({policy-id, policy-content}, {policy-id, policy-content})
133 Map<String, Map<Integer, List<Pair<String, Map<String, Object>>>>> closestMatches = new LinkedHashMap<>();
135 // Now scan the list of obligations
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);
143 LOGGER.warn("Unsupported Obligation Id {}", obligation.getId());
147 // Now add all the policies to the DecisionResponse
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());
160 protected void scanAdvice(Collection<Advice> advice, DecisionResponse decisionResponse) {
161 LOGGER.warn("scanAdvice not supported by {}", this.getClass());
165 * scanClosestMatchObligation - scans for the obligation specifically holding policy
166 * contents and their details.
168 * @param closestMatches Map holding the current set of highest weight policy types
169 * @param Obligation Obligation object
171 protected void scanClosestMatchObligation(
172 Map<String, Map<Integer, List<Pair<String, Map<String, Object>>>>> closestMatches, Obligation obligation) {
174 // Create our OnapObligation object
176 OnapObligation onapObligation = new OnapObligation(obligation);
178 // All 4 *should* be there
180 if (onapObligation.getPolicyId() == null || onapObligation.getPolicyContent() == null
181 || onapObligation.getPolicyType() == null || onapObligation.getWeight() == null) {
182 LOGGER.error("Missing an expected attribute in obligation.");
188 String policyId = onapObligation.getPolicyId();
189 String policyType = onapObligation.getPolicyType();
190 Map<String, Object> policyContent = onapObligation.getPolicyContentAsMap();
191 int policyWeight = onapObligation.getWeight();
193 // If the Policy Type exists, get the weight map.
195 Map<Integer, List<Pair<String, Map<String, Object>>>> weightMap = closestMatches.get(policyType);
196 if (weightMap != null) {
198 // Only need to check first one - as we will ensure there is only one weight
200 Entry<Integer, List<Pair<String, Map<String, Object>>>> firstEntry =
201 weightMap.entrySet().iterator().next();
202 if (policyWeight < firstEntry.getKey()) {
204 // Existing policies have a greater weight, so we will not add it
206 LOGGER.info("{} is lesser weight {} than current policies, will not return it", policyWeight,
207 firstEntry.getKey());
208 } else if (firstEntry.getKey().equals(policyWeight)) {
210 // Same weight - we will add it
212 LOGGER.info("Same weight {}, adding policy", policyWeight);
213 firstEntry.getValue().add(Pair.of(policyId, policyContent));
216 // The weight is greater, so we need to remove the other policies
217 // and point to this one.
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));
223 weightMap.put(policyWeight, listPolicies);
227 // Create a new entry
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);
239 public Object convertPolicy(ToscaPolicy toscaPolicy) throws ToscaPolicyConversionException {
241 // Get the TOSCA Policy Type for this policy
243 ToscaServiceTemplate toscaPolicyTypeTemplate = this.findPolicyType(toscaPolicy.getTypeIdentifier());
245 // If we don't have any TOSCA policy types, then we cannot know
246 // which properties are matchable.
248 if (toscaPolicyTypeTemplate == null) {
249 throw new ToscaPolicyConversionException(
250 "Cannot retrieve Policy Type definition for policy " + toscaPolicy.getName());
253 // Policy name should be at the root
255 String policyName = toscaPolicy.getMetadata().get(POLICY_ID);
257 // Set it as the policy ID
259 PolicyType newPolicyType = new PolicyType();
260 newPolicyType.setPolicyId(policyName);
262 // Optional description
264 newPolicyType.setDescription(toscaPolicy.getDescription());
266 // There should be a metadata section
268 fillMetadataSection(newPolicyType, toscaPolicy.getMetadata());
270 // Set the combining rule
272 newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_FIRST_APPLICABLE.stringValue());
274 // Generate the TargetType - the policy should not be evaluated
275 // unless all the matchable properties it cares about are matched.
277 Pair<TargetType, Integer> pairGenerated = generateTargetType(toscaPolicy, toscaPolicyTypeTemplate);
278 newPolicyType.setTarget(pairGenerated.getLeft());
280 // Now represent the policy as Json
282 StandardCoder coder = new StandardCoder();
285 jsonPolicy = coder.encode(toscaPolicy);
286 } catch (CoderException e) {
287 throw new ToscaPolicyConversionException("Failed to encode policy to json", e);
290 // Add it as an obligation
292 addObligation(newPolicyType, policyName, jsonPolicy, pairGenerated.getRight(), toscaPolicy.getType());
294 // Now create the Permit Rule.
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());
302 // The rule contains the Condition which adds logic for
303 // optional policy-type filtering.
305 rule.setCondition(generateConditionForPolicyType(toscaPolicy.getType()));
307 // Add the rule to the policy
309 newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(rule);
311 // Log output of the policy
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);
322 return newPolicyType;
326 public ToscaPolicyType retrievePolicyType(String derivedFrom) {
327 ToscaServiceTemplate template = this.findPolicyType(new ToscaPolicyTypeIdentifier(derivedFrom, "1.0.0"));
328 if (template == null) {
329 LOGGER.error("Could not retrieve Policy Type {}", derivedFrom);
332 return template.getPolicyTypes().get(derivedFrom);
336 public ToscaDataType retrieveDataType(String datatype) {
338 // Our outer class is not storing the current template being scanned
340 LOGGER.error("this retrieveDataType should not be called.");
344 private class MyMatchableCallback implements MatchableCallback {
345 private StdMatchableTranslator translator;
346 private ToscaServiceTemplate template;
348 public MyMatchableCallback(StdMatchableTranslator translator, ToscaServiceTemplate template) {
349 this.translator = translator;
350 this.template = template;
354 public ToscaPolicyType retrievePolicyType(String derivedFrom) {
355 ToscaPolicyType policyType = this.template.getPolicyTypes().get(derivedFrom);
356 if (policyType != null) {
359 return translator.retrievePolicyType(derivedFrom);
363 public ToscaDataType retrieveDataType(String datatype) {
364 return this.template.getDataTypes().get(datatype);
370 * For generating target type, we scan for matchable properties
371 * and use those to build the policy.
373 * @param properties Properties section of policy
374 * @param policyTypes Collection of policy Type to find matchable metadata
375 * @return {@code Pair<TargetType, Integer>} Returns a TargetType and a Total Weight of matchables.
377 protected Pair<TargetType, Integer> generateTargetType(ToscaPolicy policy, ToscaServiceTemplate template) {
381 TargetType target = new TargetType();
383 // See if we have a matchable in the cache already
385 MatchablePolicyType matchablePolicyType = matchableCache.get(policy.getTypeIdentifier());
387 // If not found, create one
389 if (matchablePolicyType == null) {
393 MyMatchableCallback myCallback = new MyMatchableCallback(this, template);
395 // Create the matchable
397 matchablePolicyType = new MatchablePolicyType(
398 template.getPolicyTypes().get(policy.getType()), myCallback);
402 matchableCache.put(policy.getTypeIdentifier(), matchablePolicyType);
405 // Fill in the target type with potential matchables
408 fillTargetTypeWithMatchables(target, matchablePolicyType, policy.getProperties());
409 } catch (ToscaPolicyConversionException e) {
410 LOGGER.error("Could not generate target type", e);
413 // There may be a case for default policies there is no weight - need to clean
414 // up the target then else PDP will report bad policy missing AnyOf
416 int weight = calculateWeight(target);
417 LOGGER.debug("Weight is {} for policy {}", weight, policy.getName());
419 // Assume the number of AllOf's is the weight for now
421 return Pair.of(target, weight);
424 @SuppressWarnings("unchecked")
425 protected void fillTargetTypeWithMatchables(TargetType target, MatchablePolicyType matchablePolicyType,
426 Map<String, Object> properties) throws ToscaPolicyConversionException {
427 for (Entry<String, Object> entrySet : properties.entrySet()) {
428 String propertyName = entrySet.getKey();
429 Object propertyValue = entrySet.getValue();
430 MatchableProperty matchable = matchablePolicyType.get(propertyName);
431 if (matchable != null) {
433 // Construct attribute id
435 Identifier id = new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + propertyName);
437 // Depending on what type it is, add it into the target
439 ToscaPolicyTranslatorUtils.buildAndAppendTarget(target,
440 matchable.getType().generate(propertyValue, id));
445 // Here is the special case where we look for a Collection of values that may
446 // contain potential matchables
448 if (propertyValue instanceof List) {
449 for (Object listValue : ((List<?>) propertyValue)) {
450 if (listValue instanceof Map) {
451 fillTargetTypeWithMatchables(target, matchablePolicyType, (Map<String, Object>) listValue);
454 } else if (propertyValue instanceof Map) {
455 fillTargetTypeWithMatchables(target, matchablePolicyType, (Map<String, Object>) propertyValue);
460 protected int calculateWeight(TargetType target) {
462 for (AnyOfType anyOf : target.getAnyOf()) {
463 for (AllOfType allOf : anyOf.getAllOf()) {
464 weight += allOf.getMatch().size();
472 * findPolicyType - given the ToscaPolicyTypeIdentifier, finds it in memory, or
473 * then tries to find it either locally on disk or pull it from the Policy
474 * Lifecycle API the given TOSCA Policy Type.
476 * @param policyTypeId ToscaPolicyTypeIdentifier to find
477 * @return ToscaPolicyType object. Can be null if failure.
479 private ToscaServiceTemplate findPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
481 // Is it loaded in memory?
483 ToscaServiceTemplate policyTemplate = this.matchablePolicyTypes.get(policyTypeId);
484 if (policyTemplate == null) {
488 policyTemplate = this.loadPolicyType(policyTypeId);
492 if (policyTemplate != null) {
493 this.matchablePolicyTypes.put(policyTypeId, policyTemplate);
499 return policyTemplate;
503 * loadPolicyType - Tries to load the given ToscaPolicyTypeIdentifier from local
504 * storage. If it does not exist, will then attempt to pull from Policy Lifecycle
507 * @param policyTypeId ToscaPolicyTypeIdentifier input
508 * @return ToscaPolicyType object. Null if failure.
510 private ToscaServiceTemplate loadPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
512 // Construct what the file name should be
514 Path policyTypePath = this.constructLocalFilePath(policyTypeId);
521 // If it exists locally, read the bytes in
523 bytes = Files.readAllBytes(policyTypePath);
524 } catch (IOException e) {
526 // Does not exist locally, so let's GET it from the policy api
528 LOGGER.error("PolicyType not found in data area yet {}", policyTypePath, e);
530 // So let's pull it from API REST call and save it locally
532 return this.pullPolicyType(policyTypeId, policyTypePath);
535 // Success - we have read locally the policy type. Now bring it into our
538 LOGGER.info("Read in local policy type {}", policyTypePath.toAbsolutePath());
541 // Decode the template
543 ToscaServiceTemplate template = standardYamlCoder.decode(new String(bytes, StandardCharsets.UTF_8),
544 ToscaServiceTemplate.class);
546 // Ensure all the fields are setup correctly
548 JpaToscaServiceTemplate jtst = new JpaToscaServiceTemplate();
549 jtst.fromAuthorative(template);
550 return jtst.toAuthorative();
551 } catch (CoderException e) {
552 LOGGER.error("Failed to decode tosca template for {}", policyTypePath, e);
555 // Hopefully we never get here
557 LOGGER.error("Failed to find/load policy type {}", policyTypeId);
562 * pullPolicyType - pulls the given ToscaPolicyTypeIdentifier from the Policy Lifecycle API.
563 * If successful, will store it locally given the policyTypePath.
565 * @param policyTypeId ToscaPolicyTypeIdentifier
566 * @param policyTypePath Path object to store locally
567 * @return ToscaPolicyType object. Null if failure.
569 private synchronized ToscaServiceTemplate pullPolicyType(ToscaPolicyTypeIdentifier policyTypeId,
570 Path policyTypePath) {
572 // This is what we return
574 ToscaServiceTemplate policyTemplate = null;
576 PolicyApiCaller api = new PolicyApiCaller(this.apiRestParameters);
578 policyTemplate = api.getPolicyType(policyTypeId);
579 } catch (PolicyApiException e) {
580 LOGGER.error("Failed to make API call", e);
581 LOGGER.error("parameters: {} ", this.apiRestParameters);
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 ToscaPolicyTypeIdentifier
604 * @return Path object
606 private Path constructLocalFilePath(ToscaPolicyTypeIdentifier policyTypeId) {
607 return Paths.get(this.pathForData.toAbsolutePath().toString(), policyTypeId.getName() + "-"
608 + policyTypeId.getVersion() + ".yaml");