/* * @(#)PolicyCollection.java * * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistribution of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistribution in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed or intended for use in * the design, construction, operation or maintenance of any nuclear facility. */ package com.sun.xacml.support.finder; import com.sun.xacml.AbstractPolicy; import com.sun.xacml.EvaluationCtx; import com.sun.xacml.MatchResult; import com.sun.xacml.Policy; import com.sun.xacml.PolicyReference; import com.sun.xacml.PolicySet; import com.sun.xacml.PolicyTreeElement; import com.sun.xacml.Target; import com.sun.xacml.TargetSection; import com.sun.xacml.VersionConstraints; import com.sun.xacml.combine.PolicyCombiningAlgorithm; import com.sun.xacml.ctx.Result; import com.sun.xacml.ctx.Status; import java.io.Serializable; import java.net.URI; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.StringTokenizer; import java.util.TreeSet; /** * This class handles collections of <code>AbstractPolicy</code> instances, * and provides some commonly useful operations. Specifically, it lets you * retrieve matching policies (based on reference or context), it optionally * handles wrapping multiple matches under a single PolicySet, and it manages * different versions of policies correctly. This class is intended for use * as a backing store to <code>PolicyFinderModule</code>s, but in practice * may have many uses. * <p> * Note that this class will accept multiple versions of the same policy. This * means that when you retieve a policy by reference, you will get the * correct version. It also means that when you retrieve a policy based on * context, there may be multiple revisions of the same policy, any number * of which may apply. Generally speaking, the correct behavior here is not * to return all of these policies, since they are (virtually speaking) the * same policy, but may have conflicting rules. So, as a simplification, and * to handle the most common cases, only the most recent version of a policy * is returned in these cases. If you need a more complex solution, you * will need to implement it yourself. Because the support modules use this * class as their backing store, this is true also of those modules. * <p> * Note that this is not a heavily optimized class. It is intended more as * an example, support code for the finder modules, and a starting utility * for other programmers than as an enterprise-quality implementation. That * said, it is fully functional, and should be useful for many applications. * * @since 2.0 * @author Seth Proctor */ public class PolicyCollection { // the actual collection of policies private HashMap<String, TreeSet<AbstractPolicy> > policies; // the single instance of the comparator we'll use for managing versions private VersionComparator versionComparator = new VersionComparator(); // the optional combining algorithm used when wrapping multiple policies private PolicyCombiningAlgorithm combiningAlg; // the optional policy id used when wrapping multiple policies private URI parentId; // default target that matches anything, used in wrapping policies private static final Target target; /** * This static initializer just sets up the default target, which is * used by all wrapping policy sets. */ static { target = new Target(new ArrayList<TargetSection>()); } /** * Creates a new <code>PolicyCollection</code> that will return errors * when multiple policies match for a given request. */ public PolicyCollection() { this.policies = new HashMap<String, TreeSet<AbstractPolicy>>(); this.combiningAlg = null; } /** * Creates a new <code>PolicyCollection</code> that will create a new * top-level PolicySet when multiple policies match for a given request. * * @param combiningAlg the algorithm to use in a new PolicySet when more * than one policy applies * @param parentPolicyId the identifier to use for the new PolicySet */ public PolicyCollection(PolicyCombiningAlgorithm combiningAlg, URI parentPolicyId) { this.policies = new HashMap<String, TreeSet<AbstractPolicy> >(); this.combiningAlg = combiningAlg; this.parentId = parentPolicyId; } /** * Adds a new policy to the collection, and uses the policy's identifier * as the reference identifier. If this identifier already exists in the * collection, and this policy does not represent a new version of the * policy, then the policy is not added. * * @param policy the policy to add * * @return true if the policy was added, false otherwise */ public boolean addPolicy(AbstractPolicy policy) { return addPolicy(policy, policy.getId().toString()); } /** * Adds a new policy to the collection using the given identifier as * the reference identifier. If this identifier already exists in the * collection, and this policy does not represent a new version of the * policy, then the policy is not added. * * @param policy the policy to add * @param identifier the identifier to use when referencing this policy * * @return true if the policy was added, false otherwise */ public boolean addPolicy(AbstractPolicy policy, String identifier) { if (this.policies.containsKey(identifier)) { // this identifier is already is use, so see if this version is // already in the set TreeSet<AbstractPolicy> set = this.policies.get(identifier); return set.add(policy); } // this identifier isn't already being used, so create a new // set in the map for it, and add the policy TreeSet<AbstractPolicy> set = new TreeSet<AbstractPolicy> (this.versionComparator); this.policies.put(identifier, set); return set.add(policy); } /** * Attempts to retrieve a policy based on the given context. If multiple * policies match then this will either throw an exception or wrap the * policies under a new PolicySet (depending on how this instance was * constructed). If no policies match, then this will return null. See * the comment in the class header about how this behaves when multiple * versions of the same policy exist. * * @param context representation of a request * * @return a matching policy, or null if no policy matches * * @throws TopLevelPolicyException if multiple policies match but this * instance wasn't setup to wrap policies */ public AbstractPolicy getPolicy(EvaluationCtx context) throws TopLevelPolicyException { // setup a list of matching policies ArrayList<PolicyTreeElement> list = new ArrayList<PolicyTreeElement>(); // get an iterator over all the identifiers Iterator<TreeSet<AbstractPolicy>> it = this.policies.values().iterator(); while (it.hasNext()) { // for each identifier, get only the most recent policy AbstractPolicy policy = it.next().first(); // see if we match context.newEvent(policy); MatchResult match = policy.match(context); int result = match.getResult(); // if there was an error, we stop right away if (result == MatchResult.INDETERMINATE) { context.closeCurrentEvent( new Result(Result.DECISION_INDETERMINATE, context)); throw new TopLevelPolicyException(match.getStatus()); } if (result == MatchResult.NO_MATCH) { context.closeCurrentEvent( new Result(Result.DECISION_NOT_APPLICABLE)); } // if we matched, we keep track of the matching policy... if (result == MatchResult.MATCH) { context.closeCurrentEvent(); // ...first checking if this is the first match and if // we automaticlly nest policies if ((this.combiningAlg == null) && (list.size() > 0)) { ArrayList<String> code = new ArrayList<String>(); code.add(Status.STATUS_PROCESSING_ERROR); Status status = new Status(code, "too many applicable" + " top-level policies"); throw new TopLevelPolicyException(status); } list.add(policy); } } // no errors happened during the search, so now take the right // action based on how many policies we found switch (list.size()) { case 0: return null; case 1: return ((AbstractPolicy)(list.get(0))); default: return new PolicySet(this.parentId, this.combiningAlg, target, list); } } /** * Attempts to retrieve a policy based on the given identifier and other * constraints. If there are multiple versions of the identified policy * that meet the version constraints, then the most recent version is * returned. * * @param identifier an identifier specifying some policy * @param type type of reference (policy or policySet) as identified by * the fields in <code>PolicyReference</code> * @param constraints any optional constraints on the version of the * referenced policy (this will never be null, but * it may impose no constraints, and in fact will * never impose constraints when used from a pre-2.0 * XACML policy) * * @return the policy/policy set that was retrieved. */ public AbstractPolicy getPolicy(String identifier, int type, VersionConstraints constraints) { TreeSet<AbstractPolicy> set = this.policies.get(identifier); // if we don't know about this identifier then there's nothing to do if (set == null) { return null; } // walk through the set starting with the most recent version, looking // for a match until we exhaust all known versions Iterator<AbstractPolicy> it = set.iterator(); while (it.hasNext()) { AbstractPolicy policy = it.next(); if (constraints.meetsConstraint(policy.getVersion())) { // we found a valid version, so see if it's the right kind, // and if it is then we return it if (type == PolicyReference.POLICY_REFERENCE) { if (policy instanceof Policy) { return policy; } } else { if (policy instanceof PolicySet) { return policy; } } } } // we didn't find a match return null; } /** * A <code>Comparator</code> that is used within this class to maintain * ordering amongst different versions of the same policy. Note that * it actually maintains reverse-ordering, since we want to traverse the * sets in decreasing, not increasing order. * <p> * Note that this comparator is only used when there are multiple versions * of the same policy, which in practice will probably happen far less * (from this class' point of view) than additions or fetches. */ static class VersionComparator implements Comparator<AbstractPolicy>, Serializable { /** * Serial version UID. */ private static final long serialVersionUID = 1L; public int compare(AbstractPolicy o1, AbstractPolicy o2) { // we swap the parameters so that sorting goes largest to smallest String v1 = o2.getVersion(); String v2 = o1.getVersion(); // do a quick check to see if the strings are equal (note that // even if the strings aren't equal, the versions can still // be equal) if (v1.equals(v2)) { return 0; } // setup tokenizers, and walk through both strings one set of // numeric values at a time StringTokenizer tok1 = new StringTokenizer(v1, "."); StringTokenizer tok2 = new StringTokenizer(v2, "."); while (tok1.hasMoreTokens()) { // if there's nothing left in tok2, then v1 is bigger if (! tok2.hasMoreTokens()) { return 1; } // get the next elements in the version, convert to numbers, // and compare them (continuing with the loop only if the // two values were equal) int num1 = Integer.parseInt(tok1.nextToken()); int num2 = Integer.parseInt(tok2.nextToken()); if (num1 > num2) { return 1; } if (num1 < num2) { return -1; } } // if there's still something left in tok2, then it's bigger if (tok2.hasMoreTokens()) { return -1; } // if we got here it means both versions had the same number of // elements and all the elements were equal, so the versions // are in fact equal return 0; } } }