001/**
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 2006 Sun Microsystems Inc. All Rights Reserved
005 *
006 * The contents of this file are subject to the terms
007 * of the Common Development and Distribution License
008 * (the License). You may not use this file except in
009 * compliance with the License.
010 *
011 * You can obtain a copy of the License at
012 * https://opensso.dev.java.net/public/CDDLv1.0.html or
013 * opensso/legal/CDDLv1.0.txt
014 * See the License for the specific language governing
015 * permission and limitations under the License.
016 *
017 * When distributing Covered Code, include this CDDL
018 * Header Notice in each file and include the License file
019 * at opensso/legal/CDDLv1.0.txt.
020 * If applicable, add the following below the CDDL Header,
021 * with the fields enclosed by brackets [] replaced by
022 * your own identifying information:
023 * "Portions Copyrighted [year] [name of copyright owner]"
024 *
025 * $Id: PolicyDecision.java,v 1.3 2008/06/25 05:43:44 qcheng Exp $
026 *
027 */
028
029/*
030 * Portions Copyrighted [2011] [ForgeRock AS]
031 */
032package com.sun.identity.policy;
033
034import java.util.Map;
035import java.util.HashMap;
036import java.util.Set;
037import java.util.Iterator;
038import java.util.Collections;
039import com.sun.identity.shared.xml.XMLUtils;
040import com.sun.identity.sm.AttributeSchema;
041import org.w3c.dom.*;
042
043
044/**
045 * The <code>PolicyDecision</code> class represents the result of a policy
046 * evaluation. 
047 *
048 * @supported.api
049 */ 
050public class PolicyDecision {
051
052    static final String POLICY_DECISION = "PolicyDecision";
053    static final String RESPONSE_DECISIONS = "ResponseDecisions";
054    
055    //added in 7.0 for attributes provided by policy response providers
056    static final String RESPONSE_ATTRIBUTES = "ResponseAttributes"; 
057
058    Map actionDecisions = new HashMap();
059    private Map responseDecisions;
060    private Map responseAttributes;
061    private long timeToLive = Long.MAX_VALUE;
062    private boolean advicesAreSet = false;
063
064    /**
065     * Default constructor.
066     * @supported.api
067     */
068    public PolicyDecision() {
069    }
070
071    /**
072     * Gets the  <code>Map</code> of action decisions associated 
073     * with this policy decision. The 
074     * action name is the key to the Map. The value for each key is an 
075     * <code>ActionDecision</code>.
076     *
077     * @return  the  <code>Map</code> of action decisions associated 
078     *         with this policy decision. The 
079     *         action name is the key to the Map. The value for each key is an 
080     *         <code>ActionDecision</code>.
081     * @supported.api
082     */
083    public Map getActionDecisions() {
084        return actionDecisions;
085    }
086
087    /**
088     * Add an <code>ActionDecision</code> to the <code>PolicyDecision</code>
089     * These are the rules followed to add action decision:
090     * If the action schema has boolean syntax, boolean false value
091     * overrides boolean true value. The time to live of boolean false 
092     * value overrides the time to live of boolean true value.
093     * Otherwise, action values are simply  aggregated. Time to live
094     * is set to the minimum of time to live(s) of all values of the
095     * action.
096     *
097     * @param newActionDecision an <code>ActionDecision</code> to be added.
098     * @param resourceType <code>ServiceType</code> representing the
099     * service which provides the schema for the action.
100     * 
101     */
102
103    void addActionDecision(ActionDecision newActionDecision, ServiceType
104            resourceType) {
105        String action = newActionDecision.getActionName();
106        ActionDecision oldActionDecision 
107                = (ActionDecision) actionDecisions.get(action);
108        if ( oldActionDecision == null ) {
109            addActionDecision(newActionDecision);
110        } else {
111            ActionSchema actionSchema = null;
112            AttributeSchema.Syntax actionSyntax = null;
113            try {
114              actionSchema = resourceType.getActionSchema(action);
115              actionSyntax = actionSchema.getSyntax();
116            } catch(InvalidNameException e) {
117              PolicyManager.debug.error(
118                    "can not find action schmea for action = " 
119                     + action, e );
120            }
121            if (!AttributeSchema.Syntax.BOOLEAN.equals(
122                    actionSyntax)) {
123              addActionDecision(newActionDecision);
124            } else { //boolean valued action
125              String falseValue = actionSchema.getFalseValue();
126              String trueValue = actionSchema.getTrueValue();
127              addActionDecision(newActionDecision, trueValue, falseValue);
128            }
129        }
130    }
131
132    /**
133     * Add an <code>ActionDecision</code> to the <code>PolicyDecision</code>
134     * using the provided <code>trueValue</code> and <code>falseValue</code>
135     * These are the rules followed to add action decision:
136     * Boolean false value overrides boolean true value. The time to live 
137     * of boolean false value overrides the time to live of boolean true value.
138     * Otherwise, action values are simply  aggregated. Time to live
139     * is set to the minimum of time to live(s) of all values of the
140     * action.
141     *
142     * @param newActionDecision an <code>ActionDecision</code> to be added.
143     * @param trueValue <code>String</code> representing the </code>true</code>
144     * value in the action schema.
145     * @param falseValue <code>String</code> representing the 
146     * </code>false</code> value in the action schema.
147     * 
148     */
149    public void addActionDecision(ActionDecision newActionDecision, 
150            String trueValue, String falseValue) {
151        String action = newActionDecision.getActionName();
152        ActionDecision oldActionDecision 
153                = (ActionDecision) actionDecisions.get(action);
154        if ( (oldActionDecision == null) 
155                || (trueValue == null) || (falseValue == null)) {
156            addActionDecision(newActionDecision);
157        } else { //boolean valued action
158            long newTtl = newActionDecision.getTimeToLive();
159            long oldTtl = oldActionDecision.getTimeToLive();
160            Set oldActionValues = oldActionDecision.getValues();
161            Set newActionValues = newActionDecision.getValues();
162            Map advices = null;
163            Map oldAdvices = oldActionDecision.getAdvices();
164            Map newAdvices = newActionDecision.getAdvices();
165            advices = PolicyUtils.addMapToMap(oldAdvices, newAdvices);
166            if ( (oldActionValues != null) 
167                    && (oldActionValues.contains(falseValue)) ) {
168              if ( (newActionValues != null) 
169                        && newActionValues.contains(falseValue) ) {
170                  //both old and new values are false
171                  //get the ttl to max of newTtl and oldTtl
172                  oldActionDecision.setTimeToLive(Math.max(newTtl, oldTtl));
173              }
174
175              /* else block not required here since
176                 oldActionDecision does not need to change as it is false
177                 and newActionDecision is null or true
178              */
179            } else if ( (oldActionValues != null)
180                    && oldActionValues.contains(trueValue) ) {
181              if ( (newActionValues != null)
182                        && newActionValues.contains(falseValue) ) {
183                  actionDecisions.put(action, newActionDecision);
184              } else if ( newActionDecision.getValues().contains(trueValue) ) {
185                  //get the ttl to max of newTtl and oldTtl
186                  oldActionDecision.setTimeToLive(Math.max(newTtl, oldTtl));
187              }
188            } else {
189                  actionDecisions.put(action, newActionDecision);
190            }
191            ActionDecision ad = (ActionDecision) actionDecisions.get(action);
192            ad.setAdvices(advices);
193            setTimeToLive();
194        }
195    }
196
197    /**
198     * Adds an action decision to this object
199     * if there is already an existing actionDecision associated with the
200     * action name in the param <code>actionDecision</code>, merges
201     * the values of the new decision with the existing one,
202     * changing the time to live for the decision appropriately.
203     *
204     * @param actionDecision action decision to be added
205     * @supported.api
206     */
207    public void addActionDecision(ActionDecision actionDecision) {
208        ActionDecision oldDecision =
209                (ActionDecision) actionDecisions.get(
210                actionDecision.getActionName());
211        if ( oldDecision == null ) {
212            actionDecisions.put(actionDecision.getActionName(), 
213                    actionDecision);
214        } else {
215            Set oldValues = oldDecision.getValues();
216            if ( (oldValues == Collections.EMPTY_SET)
217                    || ( oldValues == null) ) {
218                    oldDecision.setValues(actionDecision.getValues());
219            } else {
220                oldValues.addAll(actionDecision.getValues());
221            }
222            if ( actionDecision.getTimeToLive() 
223                    < oldDecision.getTimeToLive() ) {
224                oldDecision.setTimeToLive(actionDecision.getTimeToLive());
225            }
226            PolicyUtils.appendMapToMap(actionDecision.getAdvices(),
227                    oldDecision.getAdvices());
228        }
229        setTimeToLive();
230    }
231
232    /**
233     * Gets a String representation of this <code>PolicyDecision</code>
234     * @return a String representation of this <code>PolicyDecision</code>
235     *
236     * @supported.api
237     */
238     public String toString() {
239        StringBuilder sb = new StringBuilder();
240        if ((responseAttributes != null) && 
241            (responseAttributes != Collections.EMPTY_MAP)) {
242             Iterator attrNames = responseAttributes.keySet().iterator();
243             while ( attrNames.hasNext() ) {
244                 String attrName = (String) attrNames.next();
245                 Set attrValues = (Set) responseAttributes.get(attrName);
246                 sb.append(attrName).append("=").append(attrValues).append("\n");
247             }
248        }
249        Iterator actionNames = actionDecisions.keySet().iterator();
250        while ( actionNames.hasNext() ) {
251            String actionName = (String) actionNames.next();
252            ActionDecision actionDecision = 
253                (ActionDecision) actionDecisions.get(actionName);
254            Set actionValues = (Set) actionDecision.getValues();
255            sb.append(actionName).append("=").append(actionValues).append("\n");
256        }
257        return sb.toString();
258     }
259
260
261    /**
262     * Gets an XML representation of this object
263     * @return an XML representation of this object
264     *
265     * @supported.api
266     */
267     public String toXML() {
268        StringBuilder sb  = new StringBuilder(300);
269        sb.append("<").append(POLICY_DECISION)
270               /*
271                .append(" ").append("timeToLive")
272                .append("=\"").append(timeToLive).append("\"") 
273                .append(" ").append("hasAdvices")
274                .append("=\"").append(hasAdvices()).append("\"")  
275                */
276                .append(">").append(PolicyUtils.CRLF);
277        if ((responseAttributes != null) && 
278            (responseAttributes != Collections.EMPTY_MAP)) {
279            sb.append("<").append(RESPONSE_ATTRIBUTES);
280            sb.append(">").append(PolicyUtils.CRLF);
281            sb.append(PolicyUtils.mapToXMLString(responseAttributes));
282            sb.append("<").append("/").append(RESPONSE_ATTRIBUTES);
283            sb.append(">").append(PolicyUtils.CRLF);
284        }
285        Iterator actionNames = actionDecisions.keySet().iterator();
286        while ( actionNames.hasNext() ) {
287            String actionName = (String) actionNames.next();
288            ActionDecision actionDecision = (ActionDecision)
289            actionDecisions.get(actionName);
290            sb.append(actionDecision.toXML());
291        }
292        if (responseDecisions != null) {
293            sb.append("<").append(RESPONSE_DECISIONS).append(">")
294                    .append(PolicyUtils.CRLF);
295            sb.append(PolicyUtils.mapToXMLString(responseDecisions));
296            sb.append("</").append(RESPONSE_DECISIONS).append(">")
297                    .append(PolicyUtils.CRLF);
298        }
299        sb.append("</").append(POLICY_DECISION).append(">");
300        sb.append(PolicyUtils.CRLF);
301        return sb.toString();
302     }
303
304    /**
305     * Gets a PolicyDecision given corresponding XML node
306     * @param policyDecisionNode XML node for the policy decision
307     * @return policy decision based on the XML node
308     */
309     public static PolicyDecision parsePolicyDecision(Node policyDecisionNode) 
310        throws PolicyException 
311    {
312        PolicyDecision policyDecision = new PolicyDecision();
313        Set nodeSet = XMLUtils.getChildNodes(policyDecisionNode, 
314                ActionDecision.ACTION_DECISION);
315        if (nodeSet == null) {
316            PolicyManager.debug.error("parsePolicyDecision: Required element "
317                + "not found in policy decision node:"
318                + ActionDecision.ACTION_DECISION);
319            Object [] args = { ActionDecision.ACTION_DECISION };
320            throw new PolicyException(ResBundleUtils.rbName,
321                "missing_element", args, null);
322        } else {
323            Iterator nodes = nodeSet.iterator();
324            while (nodes.hasNext()) {
325                Node node = (Node)nodes.next();
326                ActionDecision actionDecision = 
327                    ActionDecision.parseActionDecision(node);
328                policyDecision.addActionDecision(actionDecision);
329            }
330        }
331        Set resposeAttrsSet = XMLUtils.getChildNodes(policyDecisionNode, 
332                RESPONSE_ATTRIBUTES);
333        if ( (resposeAttrsSet != null) && !resposeAttrsSet.isEmpty() ) {
334            Node node = (Node) resposeAttrsSet.iterator().next();
335            Map responseAttrsMap = PolicyUtils.parseAttributeValuePairs(node);
336            policyDecision.setResponseAttributes(responseAttrsMap);
337        }
338        Set responseNodeSet = XMLUtils.getChildNodes(policyDecisionNode, 
339                RESPONSE_DECISIONS);
340        if ( (responseNodeSet != null) && !responseNodeSet.isEmpty() ) {
341            Node node = (Node) responseNodeSet.iterator().next();
342            Map responseMap = PolicyUtils.parseAttributeValuePairs(node);
343            policyDecision.setResponseDecisions(responseMap);
344        }
345        return policyDecision;
346     }
347
348    /**
349     * 
350     * Gets response decisions associated with this policy decision
351     * @return <code>Map</code> representing the response decisions associated 
352     * with this policy decision
353     *
354     */
355    public Map getResponseDecisions() {
356        return responseDecisions;
357    }
358
359    /**
360     * 
361     * Sets response decisions associated with this policy decision
362     * @param responseDecisions A <code>Map</code> representing response 
363     * decisions associated with this policy decision
364     */
365    public void setResponseDecisions(Map responseDecisions) {
366        this.responseDecisions = responseDecisions;
367    }
368
369
370    /**
371     * 
372     * Gets response attributes associated with this policy decision.
373     * Response attributes are computed as an aggregation of the return
374     * <code>Map</code>(s) of the <code>ResponseProvider</code> objects 
375     * associated with the policy obtained via the getResponseDecision() call.
376     * @return the <code>Map</code> of response attributes associated with 
377     * this policy decision.
378     *
379     */
380    public Map getResponseAttributes() {
381        return responseAttributes;
382    }
383
384    /**
385     * 
386     * Sets response attributes associated with this policy decision
387     * @param responseAttributes <code>Map</code> of attribute value pairs 
388     * associated with this policy decision.
389     */
390    public void setResponseAttributes(Map responseAttributes) {
391        this.responseAttributes = responseAttributes;
392    }
393    /**
394     * Makes a copy of this object
395     *
396     * @return a copied instance
397     */
398    public Object clone() {
399        PolicyDecision clone = new PolicyDecision();
400        clone.actionDecisions = new HashMap(actionDecisions.size());
401        Iterator actionDecisionIter = actionDecisions.keySet().iterator();
402        while (actionDecisionIter.hasNext()) {
403            String key = (String) actionDecisionIter.next();
404            ActionDecision ad = (ActionDecision) actionDecisions.get(key);
405            clone.addActionDecision((ActionDecision)ad.clone());
406        }
407
408        if (responseDecisions != null) {
409            clone.responseDecisions = new HashMap(responseDecisions.size());
410            Iterator responseDecisionsIter =
411                responseDecisions.keySet().iterator();
412            while (responseDecisionsIter.hasNext()) {
413                String key = (String) responseDecisionsIter.next();
414                clone.responseDecisions.put(key, responseDecisions.get(key));
415            }
416        }
417        if (responseAttributes != null) {
418            clone.responseAttributes = new HashMap(responseAttributes.size());
419            Iterator responseAttributesIter =
420                responseAttributes.keySet().iterator();
421            while (responseAttributesIter.hasNext()) {
422                String key = (String) responseAttributesIter.next();
423                clone.responseAttributes.put(key, responseAttributes.get(key));
424            }
425        }
426        return clone;
427    }
428
429
430    /**
431     * Gets the GMT time in milliseconds since epoch when this object is to
432     * be treated as expired. That is the policy decision would likely 
433     * be different after that time.
434     * This is computed as a result of <code>SimpleTimeCondition</code>s 
435     * specified in the <code>Policy</code> definition. 
436     *
437     * @return time to live
438     */
439    public long getTimeToLive() {
440        return timeToLive;
441    }
442
443    /**
444     * Sets the <code>timeToLive</code> value of the policy decision to the
445     * smallest of <code>timeToLive(s)<code> of contained
446     * <code>ActionDecision(s)<code>. Also sets value of
447     * <code>advicesAreSet</code>. This is set to <code>true</code>
448     * if any of the contained action decision(s) has advice defined.
449     */
450    private void setTimeToLive() {
451        timeToLive = Long.MAX_VALUE;
452        advicesAreSet = false;
453        Iterator actionDecisionIter = actionDecisions.keySet().iterator();
454        while (actionDecisionIter.hasNext()) {
455            String key = (String) actionDecisionIter.next();
456            ActionDecision ad = (ActionDecision) actionDecisions.get(key);
457            long actionTtl = ad.getTimeToLive();
458            if ( actionTtl < timeToLive) {
459                timeToLive = actionTtl;
460            }
461            advicesAreSet = advicesAreSet || 
462                 ((ad.getAdvices()) != null) && (!(ad.getAdvices().isEmpty()));
463        }
464    }
465
466    /**
467     * Sets the timeToLive value of the policy decision.
468     *
469     * @param ttl timeToLive value to be set
470     */
471    void setTimeToLive(long ttl) {
472        timeToLive = ttl;
473    }
474
475    /**
476     * Checks wether advices are set in this object
477     * @return <code>true</code>, if advices are set, else <code>false</code>
478     */
479    public boolean hasAdvices() {
480        return advicesAreSet;
481    }
482}