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: ResourceResult.java,v 1.5 2009/10/12 17:53:05 dillidorai Exp $
026 *
027 */
028
029/*
030 * Portions Copyrighted [2011] [ForgeRock AS]
031 */
032package com.sun.identity.policy;
033
034import java.util.*;
035
036import org.w3c.dom.*;
037import com.sun.identity.shared.debug.Debug;
038import com.sun.identity.shared.xml.XMLUtils;
039import com.sun.identity.policy.interfaces.ResourceName;
040
041
042/**
043 * Class that encapsulates a tree of resource names, with each node 
044 *  having an associated policy decision. 
045 * @supported.api
046 */
047public class ResourceResult {
048
049    /**
050     * Constant to indicate subtree level scope for ResourceResult evaluation
051     *
052     * @supported.api
053     */
054    public static final String SUBTREE_SCOPE = "subtree";
055
056    /**
057     * Constant to indicate strict subtree level scope for 
058     * <code>ResourceResult</code> evaluation
059     *
060     * @supported.api
061     */
062    public static final String STRICT_SUBTREE_SCOPE = "strict-subtree";
063
064    /**
065     * Constant to indicate base (self) level scope for 
066     * <code>ResourceResult</code> evaluation
067     *
068     * @supported.api
069     */
070    public static final String SELF_SCOPE = "self";
071
072    /**
073     * Constant used internally as a place holder for all encompassing root
074     * resoure name.  Any resource name is considered to be sub resource of 
075     * this resource name.
076     */
077    static final public String VIRTUAL_ROOT = "-__viRTuAl-rOot--_";
078
079    static final String RESOURCE_RESULT = "ResourceResult";
080    static final String RESOURCE_NAME = "name";
081    static final String POLICY_DEBUG_NAME = "amPolicy";
082    static final Debug DEBUG = Debug.getInstance(POLICY_DEBUG_NAME);
083
084    private String resourceName = null;
085    private PolicyDecision policyDecision = null;
086    private Set resourceResults = new HashSet();
087    private long timeToLive = Long.MAX_VALUE;
088    private boolean advicesAreSet = false;
089    private String stringForm = null;
090    private String xmlForm = null;
091
092    //tracks the envMap used to compute ResourceResult
093    private Map envMap = null; 
094
095    /**
096     * Used in remote api for result caching
097    */
098    private boolean stale = false;
099  
100    /**
101     * 
102     * No argument constructor
103     */
104    ResourceResult() {
105    }
106
107    /**
108     * Constructs a resource result given the resource name and policy decison
109     * @param resourceName resource name for this resource result
110     * @param policyDecision policy decision associated with the resource name
111     */
112    public ResourceResult(String resourceName, PolicyDecision 
113            policyDecision ) {
114        this.resourceName = resourceName;
115        setPolicyDecision(policyDecision);
116    }
117
118    /**
119     * Returns the resource name of this resource result
120     * @return resource name of this resource result
121     * @supported.api
122     */
123    public String getResourceName() {
124        return resourceName;
125    }
126
127
128    /**
129     * Sets the resource name of this resource result
130     * @param resourceName resource name for this resource result
131     */
132    void setResourceName(String resourceName) {
133        this.resourceName = resourceName;
134        this.stringForm = null;
135        this.xmlForm= null;
136    }
137
138
139    /**
140     * Returns the policy decision associated with this resource result
141     * @return policy decision associated with this resource result
142     * @supported.api
143     */
144    public PolicyDecision getPolicyDecision() {
145        return policyDecision;
146    }
147
148
149    /**
150     * Sets the policy decision for this resource result
151     * @param policyDecision policy decision for this resource result
152     */
153    public void setPolicyDecision(PolicyDecision policyDecision) {
154        this.policyDecision = policyDecision;
155        long pdTtl = policyDecision.getTimeToLive();
156        if ( pdTtl < timeToLive ) {
157            timeToLive = pdTtl;
158        }
159        advicesAreSet = advicesAreSet || policyDecision.hasAdvices();
160        this.stringForm = null;
161        this.xmlForm= null;
162    }
163
164
165    /**
166     * Returns the child resource results of this resource result
167     * @return child resource results of this resource result
168     * @supported.api
169     */
170    public Set getResourceResults() {
171        return resourceResults;
172    }
173
174
175    /**
176     * Sets the child resource results of this resource result
177     * @param resourceResults child resource results of this resource result
178     */
179    void setResourceResults(Set resourceResults) {
180        if (resourceResults == null) {
181            this.resourceResults.clear();
182        } else {
183            this.resourceResults = resourceResults;
184            if (policyDecision != null) {
185                timeToLive = policyDecision.getTimeToLive();
186            }
187            Iterator iter = resourceResults.iterator();
188            while (iter.hasNext()) {
189                ResourceResult rr = (ResourceResult)iter.next();
190                long ttl = rr.getTimeToLive();
191                if ( ttl < timeToLive) {
192                    timeToLive = ttl;
193                }
194                advicesAreSet = advicesAreSet || rr.hasAdvices();
195            }
196        }
197
198        this.stringForm = null;
199        this.xmlForm= null;
200    }
201
202    /**
203     * Converts an XML representation of resource result to ResourceResult
204     * @param resourceResultNode XML DOM node representing resource result
205     * @return <code>ResourceResult</code> object representation of resource 
206     * result
207     * @throws PolicyException if the conversion fails
208     */
209    public static ResourceResult parseResourceResult(Node resourceResultNode)
210            throws PolicyException {
211        ResourceResult resourceResult = new ResourceResult();
212        String resourceName = XMLUtils.getNodeAttributeValue(resourceResultNode,
213                RESOURCE_NAME);
214        if (resourceName == null) {
215            DEBUG.error("ResourceResult: missing attribute " + RESOURCE_NAME);
216            Object[] objs = {RESOURCE_NAME};
217            throw new PolicyException(ResBundleUtils.rbName,
218                "missing_attribute_in_resourceresult", objs, null);
219        }
220        resourceResult.setResourceName(resourceName);
221
222        Node node = XMLUtils.getChildNode(resourceResultNode, 
223                PolicyDecision.POLICY_DECISION);
224        if (node == null) {
225            DEBUG.error("ResourceResult: missing element " + 
226                    PolicyDecision.POLICY_DECISION);
227            Object[] objs = {PolicyDecision.POLICY_DECISION};
228            throw new PolicyException(ResBundleUtils.rbName,
229                "missing_attribute_in_resourceresult", objs, null);
230        } else {
231            resourceResult.setPolicyDecision(
232                    PolicyDecision.parsePolicyDecision(node)); 
233        }
234            
235        Set nodeSet = XMLUtils.getChildNodes(
236                resourceResultNode, RESOURCE_RESULT);
237        if (nodeSet != null) {
238            Iterator nodes = nodeSet.iterator();
239            while (nodes.hasNext()) {
240                node = (Node)nodes.next();
241                ResourceResult rRes = ResourceResult.parseResourceResult(node);
242                resourceResult.resourceResults.add(rRes);
243            }
244        }
245
246        return resourceResult;
247    }
248
249
250    /**
251     * Returns a string representation of this resource result 
252     * @return a string representation of this resource result
253     * @supported.api
254     */
255    public String toString() {
256        if (stringForm == null) {
257            StringBuilder sb = new StringBuilder(200);
258            sb.append("Resource Result for resourceName : ")
259                    .append(resourceName)
260                    .append(PolicyUtils.CRLF)
261                    .append("PolicyDecision : ")
262                    .append(policyDecision)
263                    .append("Nested ResourceResults : ")
264                    .append(resourceResults);
265            stringForm = sb.toString();
266        }
267        return stringForm;
268    }
269     
270    /**
271     * Returns an XML representation of this resource result 
272     * @return an XML representation of this resource result
273     * @supported.api
274     */
275    public String toXML() {
276        if (xmlForm == null) {
277            StringBuilder xmlsb = new StringBuilder(1000);
278
279            xmlsb.append("<")
280                    .append(RESOURCE_RESULT)
281                    .append(" ").append(RESOURCE_NAME)
282                    .append("=\"")
283                    .append(XMLUtils.escapeSpecialCharacters(resourceName)) 
284                    /*
285                    .append("\" ").append("timeToLive")
286                    .append("=\"").append(timeToLive).append("\"") 
287                    .append( " ").append("hasAdvices")
288                    .append("=\"").append(hasAdvices())
289                    */
290                    .append("\">")
291                    .append(PolicyUtils.CRLF);
292            if (policyDecision != null) {
293                xmlsb.append(policyDecision.toXML());
294            }
295            
296            Iterator rrIter = resourceResults.iterator();
297            while ( rrIter.hasNext() ) {
298               ResourceResult rr = (ResourceResult) rrIter.next();
299               xmlsb.append(rr.toXML());
300            }
301
302            xmlsb.append("</")
303                    .append(RESOURCE_RESULT)
304                    .append( ">")
305                    .append(PolicyUtils.CRLF); 
306
307            xmlForm = xmlsb.toString();                
308        }
309        return xmlForm;
310    }
311
312    /**
313     * Adds a resource result to the resource result sub tree rooted at
314     * this ResourceResult
315     * @param resourceResult resource result to be added
316     * @param serviceType service type of the resource result being added
317     * @throws PolicyException if the resourceResult could not be added
318     */
319    public void addResourceResult( ResourceResult resourceResult,
320            ServiceType serviceType) throws PolicyException {
321        addResourceResult(resourceResult,
322            serviceType.getResourceNameComparator());
323    }
324
325    /**
326     * Adds a resource result to the resource result sub tree rooted at
327     * this ResourceResult
328     * @param resourceResult resource result to be added
329     * @param resourceComparator resource name comparator
330     * @throws PolicyException if the resourceResult could not be added
331     */
332    public void addResourceResult( ResourceResult resourceResult,
333            ResourceName resourceComparator) throws PolicyException {
334        if (!this.isSuperResourceResultOf(resourceResult, 
335                resourceComparator)) { 
336            String[] objs = {this.resourceName, resourceResult.resourceName};
337            throw new PolicyException(ResBundleUtils.rbName,
338                    "invalid_sub_resourceresult", objs, null);
339        } else {
340            Iterator resourceResultIter = resourceResults.iterator();
341            boolean directChild = true;
342            while (resourceResultIter.hasNext()) {
343                ResourceResult rResult = 
344                        (ResourceResult) resourceResultIter.next();
345                if (rResult.isSuperResourceResultOf(resourceResult,
346                        resourceComparator)) {
347                    rResult.addResourceResult(resourceResult,
348                        resourceComparator);
349                    directChild = false;
350                    break;
351                }
352            }
353            if (directChild) {
354                Set childrenToBeMoved = new HashSet();
355                Iterator rrIter = resourceResults.iterator();
356                while (rrIter.hasNext()) {
357                    ResourceResult rResult = 
358                            (ResourceResult) rrIter.next();
359                    if (resourceResult.isSuperResourceResultOf(rResult,
360                            resourceComparator)) {
361                        childrenToBeMoved.add(rResult);
362                    }
363                }
364                resourceResults.removeAll(childrenToBeMoved);
365                resourceResult.resourceResults.addAll(childrenToBeMoved);
366                resourceResults.add(resourceResult);
367            }
368        }
369        long rrTtl =  resourceResult.getTimeToLive();
370        if ( rrTtl < timeToLive ) {
371            timeToLive = rrTtl;
372        }
373        advicesAreSet = advicesAreSet || resourceResult.hasAdvices();
374        this.stringForm = null;
375        this.xmlForm= null;
376    }
377
378    /**
379     * Marks result as stale
380     */
381    public void markStale() {
382        stale = true;
383    }
384
385    /**
386     * Determines if result is stale
387     *
388     * @return true if result is stale
389     */
390    public boolean isStale() {
391        return stale;
392    }
393
394    /**
395     * Checks if this resource result is a super resource result of
396     * the argument resource result
397     * @param resourceResult resource result for which we want to check
398     *        whether this resource result is a super resource result
399     * @param resourceComparator - resource comparator
400     * @return <code>true</code> if this resource result is a super 
401     *         resource result of resourceResult, else returns
402     *         <code>false</code>
403     * @throws PolicyException if there is any error while comparing the 
404     *         resourceResult
405     */
406    private boolean isSuperResourceResultOf(ResourceResult resourceResult,
407            ResourceName resourceComparator) throws PolicyException {
408        boolean isSuperResource = false;
409        if (VIRTUAL_ROOT.equals(resourceName)) {
410            isSuperResource = true;
411        } else if (resourceComparator != null) {
412            boolean interpretWildCard = false;
413            ResourceMatch resourceMatch =
414                resourceComparator.compare(resourceName,
415                    resourceResult.resourceName, interpretWildCard); 
416            if (resourceMatch.equals(ResourceMatch.SUB_RESOURCE_MATCH)) {
417                isSuperResource = true;
418            }
419            // Results will contain both incoming URL as well as matched policy URL with decision. 
420            // The problem is we don't know which order results will come in.
421            // Incoming URL will be missing policyDecision so we need to check resourceResult contains '*' 
422            // and if it does, check if resourceResult is parent of incoming URL and use parent's
423            // policyDecision for this object.  
424            else if (resourceResult.resourceName.indexOf('*') != -1 ) {
425                String resResultResName = resourceResult.resourceName;
426                String substrResultResName = resResultResName.substring(0, resResultResName.indexOf('*'));
427                if (resourceName.startsWith(substrResultResName)) {
428                    //check if policyDecision is null
429                    //if null, then copy policyDecision from parents
430                    if (policyDecision==null || policyDecision.getActionDecisions().isEmpty()) {
431                            policyDecision = resourceResult.policyDecision;
432                    }
433                }
434            }
435        } else {
436            isSuperResource =
437                    resourceResult.resourceName.startsWith(resourceName);
438        }
439        return isSuperResource;
440    }
441
442    /**
443     * Returns the GMT time in milliseconds since epoch when this object is to
444     * be treated as expired. That is the resource result would 
445     * likely be different after that time.
446     * This is computed as a result of time conditions specified in the Policy
447     * definitions. 
448     *
449     * @return time to live
450     */
451    public long getTimeToLive() {
452        return timeToLive;
453    }
454
455    /**
456     * Checks wether advices are set in this object
457     * @return <code>true</code>, if advices are set, else <code>false</code>
458     */
459    public boolean hasAdvices() {
460        return advicesAreSet;
461    }
462
463    /**
464     * Sets the environment map that was used while computing the 
465     * resource result
466     * @param envMap the environment map that was used while computing the 
467     * resource result
468     */
469    void setEnvMap(Map envMap) {
470        this.envMap = envMap;
471        
472    }
473
474    /**
475     * Returns the environment map that was used while computing the 
476     * resource result
477     * @param the environment map that was used while computing the 
478     * resource result
479     */
480    Map getEnvMap() {
481        return envMap;
482    }
483
484} 
485