Implement closest match algorithm
[policy/xacml-pdp.git] / applications / common / src / main / java / org / onap / policy / pdp / xacml / application / common / std / StdMatchableTranslator.java
index 66770e9..addb0df 100644 (file)
@@ -23,6 +23,7 @@
 package org.onap.policy.pdp.xacml.application.common.std;
 
 import com.att.research.xacml.api.Identifier;
+import com.att.research.xacml.api.Obligation;
 import com.att.research.xacml.api.Request;
 import com.att.research.xacml.api.XACML3;
 import com.att.research.xacml.std.IdentifierImpl;
@@ -38,6 +39,8 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -48,17 +51,20 @@ import oasis.names.tc.xacml._3_0.core.schema.wd_17.MatchType;
 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType;
 import oasis.names.tc.xacml._3_0.core.schema.wd_17.RuleType;
 import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType;
+import org.apache.commons.lang3.tuple.Pair;
 import org.onap.policy.common.endpoints.parameters.RestServerParameters;
 import org.onap.policy.common.utils.coder.CoderException;
 import org.onap.policy.common.utils.coder.StandardCoder;
 import org.onap.policy.common.utils.coder.StandardYamlCoder;
 import org.onap.policy.models.decisions.concepts.DecisionRequest;
+import org.onap.policy.models.decisions.concepts.DecisionResponse;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaProperty;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
 import org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate;
+import org.onap.policy.pdp.xacml.application.common.OnapObligation;
 import org.onap.policy.pdp.xacml.application.common.PolicyApiCaller;
 import org.onap.policy.pdp.xacml.application.common.PolicyApiException;
 import org.onap.policy.pdp.xacml.application.common.ToscaDictionary;
@@ -104,6 +110,127 @@ public class StdMatchableTranslator  extends StdBaseTranslator {
         return null;
     }
 
+    /**
+     * scanObligations - scans the list of obligations and make appropriate method calls to process
+     * obligations.
+     *
+     * @param obligations Collection of obligation objects
+     * @param decisionResponse DecisionResponse object used to store any results from obligations.
+     */
+    @Override
+    protected void scanObligations(Collection<Obligation> obligations, DecisionResponse decisionResponse) {
+        //
+        // Implementing a crude "closest match" on the results, which means we will strip out
+        // any policies that has the lower weight than any of the others.
+        //
+        // Most likely these are "default" policies with a weight of zero, but not always.
+        //
+        // It is possible to have multiple policies with an equal weight, that is desired.
+        //
+        // So we need to track each policy type separately and the weights for each policy.
+        //
+        // policy-type -> weight -> List({policy-id, policy-content}, {policy-id, policy-content})
+        //
+        Map<String, Map<Integer, List<Pair<String, Map<String, Object>>>>> closestMatches = new LinkedHashMap<>();
+        //
+        // Now scan the list of obligations
+        //
+        for (Obligation obligation : obligations) {
+            Identifier obligationId = obligation.getId();
+            LOGGER.info("Obligation: {}", obligationId);
+            if (ToscaDictionary.ID_OBLIGATION_REST_BODY.equals(obligationId)) {
+                scanClosestMatchObligation(closestMatches, obligation);
+            } else {
+                LOGGER.warn("Unsupported Obligation Id {}", obligation.getId());
+            }
+        }
+        //
+        // Now add all the policies to the DecisionResponse
+        //
+        closestMatches.forEach((thePolicyType, weightMap) ->
+            weightMap.forEach((weight, policies) ->
+                policies.forEach(policy -> {
+                    LOGGER.info("Policy {}", policy);
+                    decisionResponse.getPolicies().put(policy.getLeft(), policy.getRight());
+                })
+            )
+        );
+    }
+
+    /**
+     * scanClosestMatchObligation - scans for the obligation specifically holding policy
+     * contents and their details.
+     *
+     * @param closestMatches Map holding the current set of highest weight policy types
+     * @param Obligation Obligation object
+     */
+    protected void scanClosestMatchObligation(
+            Map<String, Map<Integer, List<Pair<String, Map<String, Object>>>>> closestMatches, Obligation obligation) {
+        //
+        // Create our OnapObligation object
+        //
+        OnapObligation onapObligation = new OnapObligation(obligation);
+        //
+        // All 4 *should* be there
+        //
+        if (onapObligation.getPolicyId() == null || onapObligation.getPolicyContent() == null
+                || onapObligation.getPolicyType() == null || onapObligation.getWeight() == null) {
+            LOGGER.error("Missing an expected attribute in obligation.");
+            return;
+        }
+        //
+        // Save the values
+        //
+        String policyId = onapObligation.getPolicyId();
+        String policyType = onapObligation.getPolicyType();
+        Map<String, Object> policyContent = onapObligation.getPolicyContentAsMap();
+        int policyWeight = onapObligation.getWeight();
+        //
+        // If the Policy Type exists, get the weight map.
+        //
+        Map<Integer, List<Pair<String, Map<String, Object>>>> weightMap = closestMatches.get(policyType);
+        if (weightMap != null) {
+            //
+            // Only need to check first one - as we will ensure there is only one weight
+            //
+            Entry<Integer, List<Pair<String, Map<String, Object>>>> firstEntry =
+                    weightMap.entrySet().iterator().next();
+            if (policyWeight < firstEntry.getKey()) {
+                //
+                // Existing policies have a greater weight, so we will not add it
+                //
+                LOGGER.info("{} is lesser weight {} than current policies, will not return it", policyWeight,
+                        firstEntry.getKey());
+            } else if (firstEntry.getKey().equals(policyWeight)) {
+                //
+                // Same weight - we will add it
+                //
+                LOGGER.info("Same weight {}, adding policy", policyWeight);
+                firstEntry.getValue().add(Pair.of(policyId, policyContent));
+            } else {
+                //
+                // The weight is greater, so we need to remove the other policies
+                // and point to this one.
+                //
+                LOGGER.info("New policy has greater weight {}, replacing {}", policyWeight, firstEntry.getKey());
+                List<Pair<String, Map<String, Object>>> listPolicies = new LinkedList<>();
+                listPolicies.add(Pair.of(policyId, policyContent));
+                weightMap.clear();
+                weightMap.put(policyWeight, listPolicies);
+            }
+        } else {
+            //
+            // Create a new entry
+            //
+            LOGGER.info("New entry {} weight {}", policyType, policyWeight);
+            List<Pair<String, Map<String, Object>>> listPolicies = new LinkedList<>();
+            listPolicies.add(Pair.of(policyId, policyContent));
+            Map<Integer, List<Pair<String, Map<String, Object>>>> newWeightMap = new LinkedHashMap<>();
+            newWeightMap.put(policyWeight, listPolicies);
+            closestMatches.put(policyType, newWeightMap);
+        }
+    }
+
     @Override
     public PolicyType convertPolicy(ToscaPolicy toscaPolicy) throws ToscaPolicyConversionException {
         //
@@ -143,7 +270,8 @@ public class StdMatchableTranslator  extends StdBaseTranslator {
         // Generate the TargetType - the policy should not be evaluated
         // unless all the matchable properties it cares about are matched.
         //
-        newPolicyType.setTarget(generateTargetType(toscaPolicy.getProperties(), toscaPolicyTypes));
+        Pair<TargetType, Integer> pairGenerated = generateTargetType(toscaPolicy.getProperties(), toscaPolicyTypes);
+        newPolicyType.setTarget(pairGenerated.getLeft());
         //
         // Now represent the policy as Json
         //
@@ -157,7 +285,7 @@ public class StdMatchableTranslator  extends StdBaseTranslator {
         //
         // Add it as an obligation
         //
-        addObligation(newPolicyType, jsonPolicy);
+        addObligation(newPolicyType, policyName, jsonPolicy, pairGenerated.getRight(), toscaPolicy.getType());
         //
         // Now create the Permit Rule.
         //
@@ -196,24 +324,28 @@ public class StdMatchableTranslator  extends StdBaseTranslator {
      *
      * @param properties Properties section of policy
      * @param policyTypes Collection of policy Type to find matchable metadata
-     * @return TargetType object
+     * @return {@code Pair<TargetType, Integer>} Returns a TargetType and a Total Weight of matchables.
      */
-    protected TargetType generateTargetType(Map<String, Object> properties, Collection<ToscaPolicyType> policyTypes) {
+    protected Pair<TargetType, Integer> generateTargetType(Map<String, Object> properties,
+            Collection<ToscaPolicyType> policyTypes) {
         TargetType targetType = new TargetType();
         //
         // Iterate the properties
         //
+        int totalWeight = 0;
         for (Entry<String, Object> entrySet : properties.entrySet()) {
             //
             // Find matchable properties
             //
             if (isMatchable(entrySet.getKey(), policyTypes)) {
                 LOGGER.info("Found matchable property {}", entrySet.getKey());
-                generateMatchable(targetType, entrySet.getKey(), entrySet.getValue());
+                int weight = generateMatchable(targetType, entrySet.getKey(), entrySet.getValue());
+                LOGGER.info("Weight is {}", weight);
+                totalWeight += weight;
             }
         }
-
-        return targetType;
+        LOGGER.info("Total weight is {}", totalWeight);
+        return Pair.of(targetType, totalWeight);
     }
 
     /**
@@ -259,29 +391,33 @@ public class StdMatchableTranslator  extends StdBaseTranslator {
 
     /**
      * generateMatchable - Given the object, generates list of MatchType objects and add them
-     * to the TargetType object.
+     * to the TargetType object. Returns a weight which is the number of AnyOf's generated. The
+     * weight can be used to further filter the results for "closest match".
      *
      * @param targetType TargetType object to add matches to
      * @param key Property key
      * @param value Object is the value - which can be a Collection or single Object
-     * @return TargetType incoming TargetType returned as a convenience
+     * @return int Weight of the match.
      */
     @SuppressWarnings("unchecked")
-    protected TargetType generateMatchable(TargetType targetType, String key, Object value) {
+    protected int generateMatchable(TargetType targetType, String key, Object value) {
+        int weight = 0;
         if (value instanceof Collection) {
             AnyOfType anyOf = generateMatches((Collection<Object>) value,
                     new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + key));
             if (! anyOf.getAllOf().isEmpty()) {
                 targetType.getAnyOf().add(anyOf);
+                weight = 1;
             }
         } else {
             AnyOfType anyOf = generateMatches(Arrays.asList(value),
                     new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + key));
             if (! anyOf.getAllOf().isEmpty()) {
                 targetType.getAnyOf().add(anyOf);
+                weight = 1;
             }
         }
-        return targetType;
+        return weight;
     }
 
     /**