You are viewing a plain text version of this content. The canonical link for it is here.
Posted to slide-dev@jakarta.apache.org by un...@apache.org on 2004/08/09 14:05:29 UTC

cvs commit: jakarta-slide/src/share/org/apache/slide/structure ActionNode.java

unico       2004/08/09 05:05:29

  Modified:    src/webdav/server/org/apache/slide/webdav/util
                        AclConstants.java PropertyHelper.java
               src/share/org/apache/slide/security SecurityImpl.java
               src/webdav/server/org/apache/slide/webdav/method
                        AclMethod.java
               src/share/org/apache/slide/structure ActionNode.java
  Log:
  add support for custom privileges thanks to Johan Stuyts (j.stuyts@hippo.nl):
  - privilege change detection and actions cache invalidation
  - "privilege-namespace" property holds optional namespace for privilege
  
  Revision  Changes    Path
  1.24      +5 -4      jakarta-slide/src/webdav/server/org/apache/slide/webdav/util/AclConstants.java
  
  Index: AclConstants.java
  ===================================================================
  RCS file: /home/cvs/jakarta-slide/src/webdav/server/org/apache/slide/webdav/util/AclConstants.java,v
  retrieving revision 1.23
  retrieving revision 1.24
  diff -u -r1.23 -r1.24
  --- AclConstants.java	5 Aug 2004 14:43:31 -0000	1.23
  +++ AclConstants.java	9 Aug 2004 12:05:28 -0000	1.24
  @@ -49,7 +49,7 @@
       String P_GROUP_MEMBERSHIP                = "group-membership";
       String P_OWNER                           = "owner";
       String P_MODIFICATIONUSER                = "modificationuser";
  -    String P_CREATIONUSER                = "creationuser";
  +    String P_CREATIONUSER                    = "creationuser";
       String P_SUPPORTED_PRIVILEGE_SET         = "supported-privilege-set";
       String P_CURRENT_USER_PRIVILEGE_SET      = "current-user-privilege-set";
       String P_ACL                             = "acl";
  @@ -59,6 +59,7 @@
       String P_PRIVILEGE_COLLECTION_SET        = "privilege-collection-set";
       String P_PRIVILEGE_MEMBER_SET            = "privilege-member-set";
       String P_PRIVILEGE_MEMBERSHIP            = "privilege-membership";
  +    String P_PRIVILEGE_NAMESPACE             = "privilege-namespace";
   
       String[] ACL_PROPERTIES = new String[] {
           P_ALTERNATE_URI_SET,
  
  
  
  1.80      +103 -40   jakarta-slide/src/webdav/server/org/apache/slide/webdav/util/PropertyHelper.java
  
  Index: PropertyHelper.java
  ===================================================================
  RCS file: /home/cvs/jakarta-slide/src/webdav/server/org/apache/slide/webdav/util/PropertyHelper.java,v
  retrieving revision 1.79
  retrieving revision 1.80
  diff -u -r1.79 -r1.80
  --- PropertyHelper.java	5 Aug 2004 15:44:58 -0000	1.79
  +++ PropertyHelper.java	9 Aug 2004 12:05:28 -0000	1.80
  @@ -29,7 +29,6 @@
   import java.util.Collections;
   import java.util.Date;
   import java.util.Enumeration;
  -import java.util.HashMap;
   import java.util.HashSet;
   import java.util.Iterator;
   import java.util.List;
  @@ -107,6 +106,8 @@
       public final static String LOCKDISCOVERY_INCL_PRINCIPAL =
           "lockdiscoveryIncludesPrincipalURL";
       
  +    public final static String PRIVILEGE_NAMESPACE = "privilege-namespace";
  +    
       private boolean lockdiscoveryIncludesPrincipalURL = true;
       
       /**
  @@ -1383,8 +1384,8 @@
                   transaction.addContent(groupoperation);
                   locktype.addContent(transaction);
               } else {
  -            	Element write = new Element(E_WRITE, DNSP);
  -            	locktype.addContent(write);
  +                Element write = new Element(E_WRITE, DNSP);
  +                locktype.addContent(write);
               }
               Element lockscope = new Element(E_LOCKSCOPE, DNSP);
               activelock.addContent(lockscope);
  @@ -1535,23 +1536,12 @@
       public XMLValue computeSupportedPrivilegeSet(NodeRevisionDescriptors revisionDescriptors, NodeRevisionDescriptor revisionDescriptor, String slideContextPath) throws ObjectLockedException, RevisionDescriptorNotFoundException, ServiceAccessException, LinkedObjectNotFoundException, AccessDeniedException, ObjectNotFoundException, LockTokenNotFoundException, JDOMException {
           Map actionAggregation = ((SecurityImpl)nsaToken.getSecurityHelper()).getActionAggregation();
           Set rootSet = new HashSet(actionAggregation.keySet());
  -        Map work = new HashMap();
           Iterator actions = actionAggregation.keySet().iterator();
           while (actions.hasNext()) {
               ActionNode a = (ActionNode)actions.next();
  -            Element sp = new Element(E_SUPPORTED_PRIVILEGE, DNSP);
  -            Element p = new Element(E_PRIVILEGE, DNSP);
  -            p.addContent(new Element(a.getPath().lastSegment(), DNSP));
  -            sp.addContent(p);
  -            work.put(a, sp);
  -        }
  -        actions = actionAggregation.keySet().iterator();
  -        while (actions.hasNext()) {
  -            ActionNode a = (ActionNode)actions.next();
               Iterator aggregates = ((Set)actionAggregation.get(a)).iterator();
               while (aggregates.hasNext()) {
                   ActionNode c = (ActionNode)aggregates.next();
  -                ((Element)work.get(a)).addContent((Element)work.get(c));
                   rootSet.remove(c);
               }
           }
  @@ -1560,22 +1550,51 @@
           p.addContent(new Element(E_ALL, DNSP));
           rootSp.addContent(p);
           Iterator roots = rootSet.iterator();
  -        while (roots.hasNext()) {
  -            rootSp.addContent((Element)work.get(roots.next()));
  -        }
  +        addElementsForAggregatedActions(rootSp, roots, actionAggregation);
           return new XMLValue(rootSp);
       }
       
       /**
  +     * Build the tree of an action with all its aggregated actions for use in
  +     * the result of <code>supported-privilege-set</code> queries.
  +     * 
  +     * This method modifies <code>parentActionElement</code>.
  +     * 
  +     * @param parentActionElement The action element to which to add the direct
  +     *                            aggregated acttions to.
  +     * @param aggregatedActions The direct aggregated actions.
  +     * @param actionAggregation A map from an action to its direct aggregated
  +     *                          actions.
  +     */
  +    private void addElementsForAggregatedActions(Element parentActionElement, Iterator aggregatedActions, Map actionAggregation) {
  +        while (aggregatedActions.hasNext()) {
  +            ActionNode a = (ActionNode)aggregatedActions.next();
  +            Element sp = new Element(E_SUPPORTED_PRIVILEGE, DNSP);
  +            Element p = new Element(E_PRIVILEGE, DNSP);
  +            Namespace actionNamespace = a.getNamespace();
  +            if (actionNamespace == null) {
  +                actionNamespace = DNSP;
  +            }
  +            p.addContent(new Element(a.getPath().lastSegment(), actionNamespace));
  +            addElementsForAggregatedActions(p, ((Set)actionAggregation.get(a)).iterator(), actionAggregation);
  +            sp.addContent(p);
  +            parentActionElement.addContent(sp);
  +        }
  +    }
  +
  +
  +    /**
        * Creates a <code>&lt;privilege&gt;</code> element containing an
        * element with the given <code>privilegeName</code>.
        *
        * @param      privilegeName  the name of the privilege.
        *
        * @return     the <code>&lt;privilege&gt;</code> element.
  +     * @throws ServiceAccessException
  +     * @throws RevisionDescriptorNotFoundException
        */
  -    private Element createPrivilege(String privilegeName) {
  -        return createPrivilege(privilegeName, true);
  +    private Element createPrivilege(ActionNode privilege, Uri privilegeUri) throws RevisionDescriptorNotFoundException, ServiceAccessException {
  +        return createPrivilege(privilege, true, privilegeUri);
       }
       
       /**
  @@ -1588,16 +1607,25 @@
        *                              otherwise the slide namespace.
        *
        * @return     the <code>&lt;privilege&gt;</code> element.
  +     * @throws ServiceAccessException
  +     * @throws RevisionDescriptorNotFoundException
        */
  -    private Element createPrivilege(String privilegeName, boolean useDavNamespace) {
  -        Element privilege = new Element(E_PRIVILEGE, DNSP);
  -        Namespace namespace = DNSP;
  -        if ( ! useDavNamespace ) {
  -            namespace = NamespaceCache.SLIDE_NAMESPACE;
  -        }
  -        Element privilegeNameElement = new Element(privilegeName, namespace);
  -        privilege.addContent(privilegeNameElement);
  -        return privilege;
  +    private Element createPrivilege(ActionNode privilege, boolean useDavNamespace, Uri privilegeUri) throws RevisionDescriptorNotFoundException, ServiceAccessException {
  +        NodeRevisionNumber latestRevisionNumber = privilegeUri.getStore().retrieveRevisionDescriptors(privilegeUri).getLatestRevision();
  +        NodeProperty privilegeNamespaceProperty = privilegeUri.getStore().retrieveRevisionDescriptor(privilegeUri, latestRevisionNumber).getProperty(PRIVILEGE_NAMESPACE, WebdavConstants.S_DAV);
  +        Namespace privilegeNamespace = null;
  +        if (privilegeNamespaceProperty != null && privilegeNamespaceProperty.getValue() instanceof String) {
  +            privilegeNamespace = Namespace.getNamespace((String) privilegeNamespaceProperty.getValue());
  +        } else {
  +            privilegeNamespace = DNSP;
  +            if ( ! useDavNamespace ) {
  +                privilegeNamespace = NamespaceCache.SLIDE_NAMESPACE;
  +            }
  +        }
  +        Element privilegeElement = new Element(E_PRIVILEGE, DNSP);
  +        Element privilegeNameElement = new Element(privilege.getPath().lastSegment(), privilegeNamespace);
  +        privilegeElement.addContent(privilegeNameElement);
  +        return privilegeElement;
       }
       
       /**
  @@ -1635,12 +1663,7 @@
               Uri actionsPathUri = nsaToken.getUri(sToken, actionsPath);
               ObjectNode actionsPathNode = actionsPathUri.getStore().retrieveObject(actionsPathUri);
               Enumeration actions = actionsPathNode.enumerateChildren();
  -            while (actions.hasMoreElements()) {
  -                ActionNode aNode = ActionNode.getActionNode((String)actions.nextElement());
  -                if (security.hasPermission(sToken, object, aNode)) {
  -                    xmlValue.add(createPrivilege(aNode.getPath().lastSegment()));
  -                }
  -            }
  +            addGrantedActionsToPrivilegeSet(xmlValue, object, actions);
           }
           catch (ServiceAccessException e) {
               throw e;
  @@ -1652,6 +1675,35 @@
       }
       
       /**
  +     * Build a set of privileges a subject has for an object for use in the
  +     * result of <code>current-user-privilege-set</code> queries.
  +     * 
  +     * This method modifies <code>xmlValue</code>.
  +     * 
  +     * @param xmlValue The element to which to add the actions which have been
  +     *                 granted to the subject.
  +     * @param object The object for which to determine which actions have been
  +     *               granted.
  +     * @param actions The URIs (as <code>String</code>s) of the actions to
  +     *                check.
  +     */
  +    private void addGrantedActionsToPrivilegeSet(XMLValue xmlValue, ObjectNode object, Enumeration actions) throws ServiceAccessException, ObjectNotFoundException, RevisionDescriptorNotFoundException {
  +        while (actions.hasMoreElements()) {
  +            Uri aNodeUri = nsaToken.getUri(sToken, (String)actions.nextElement());
  +            ObjectNode oNode = aNodeUri.getStore().retrieveObject(aNodeUri);
  +            if (oNode.hasChildren()) {
  +                addGrantedActionsToPrivilegeSet(xmlValue, object, oNode.enumerateChildren());
  +            } else {
  +                ActionNode aNode = ActionNode.getActionNode(oNode.getUri());
  +                if (nsaToken.getSecurityHelper().hasPermission(sToken, object, aNode)) {
  +                    xmlValue.add(createPrivilege(aNode, aNodeUri));
  +                }
  +            }
  +        }
  +    }
  +
  +
  +    /**
        * Returns an XMLValue containing the value of the
        * <code>&lt;acl&gt;</code> property.
        *
  @@ -1784,14 +1836,25 @@
           return principalElm;
       }
       
  -    private Element createPrivilegeElement(String actionUri) {
  +    private Element createPrivilegeElement(String actionUriAsString) throws RevisionDescriptorNotFoundException, ServiceAccessException {
           Element privilegeElm = new Element(E_PRIVILEGE, DNSP);
  -        if (actionUri == ActionNode.ALL_URI) {
  +        if (actionUriAsString.equals(ActionNode.ALL_URI)) {
               Element allElm = new Element(E_ALL, DNSP);
               privilegeElm.addContent(allElm);
           }
           else {
  -            Element actionElm = new Element(new UriPath(actionUri).lastSegment(), DNSP);
  +            Uri actionUri = nsaToken.getUri(sToken, actionUriAsString);
  +            NodeRevisionNumber latestRevisionNumber = actionUri.getStore().retrieveRevisionDescriptors(actionUri).getLatestRevision();
  +            NodeProperty privilegeNamespace = actionUri.getStore().retrieveRevisionDescriptor(actionUri, latestRevisionNumber).getProperty(PRIVILEGE_NAMESPACE, WebdavConstants.S_DAV);
  +            Namespace actionNamespace = null;
  +            if (privilegeNamespace != null && privilegeNamespace.getValue() instanceof String) {
  +                actionNamespace = Namespace.getNamespace((String) privilegeNamespace.getValue());
  +            }
  +            else {
  +                actionNamespace = DNSP;
  +            }
  +
  +            Element actionElm = new Element(new UriPath(actionUriAsString).lastSegment(), actionNamespace);
               privilegeElm.addContent(actionElm);
           }
           return privilegeElm;
  
  
  
  1.53      +271 -85   jakarta-slide/src/share/org/apache/slide/security/SecurityImpl.java
  
  Index: SecurityImpl.java
  ===================================================================
  RCS file: /home/cvs/jakarta-slide/src/share/org/apache/slide/security/SecurityImpl.java,v
  retrieving revision 1.52
  retrieving revision 1.53
  diff -u -r1.52 -r1.53
  --- SecurityImpl.java	5 Aug 2004 15:44:56 -0000	1.52
  +++ SecurityImpl.java	9 Aug 2004 12:05:29 -0000	1.53
  @@ -70,6 +70,7 @@
       
       private static final String LOG_CHANNEL = SecurityImpl.class.getName();
       private static final String PRIVILEGE_MEMBER_SET = "privilege-member-set";
  +    private static final String PRIVILEGE_NAMESPACE = "privilege-namespace";
       protected Logger logger;
       
       
  @@ -94,7 +95,6 @@
           this.rolesCache = new Hashtable();
           aclInheritanceType = namespaceConfig.getAclInheritanceType();
           logger = namespace.getLogger();
  -        loadActionsCache(namespace, namespaceConfig);
       }
       
       // ----------------------------------------------------- Instance Variables
  @@ -127,6 +127,20 @@
       private Map actionAggregation;        // actionNode -> Set-of-aggregated-nodes (direct aggregates)
       private Map actionAggregationClosure; // actionNode -> Set-of-aggregated-nodes (transitive closure)
       
  +    /**
  +     * If during the loading of the actions cache an error occurs, the cache
  +     * state will remain 'not loaded'. To prevent that a new attempt is made to
  +     * load the actions cache (which will probably fail too) each time the
  +     * actions cache is accessed, this flag will be set.
  +     */
  +    private boolean hasActionsCacheLoadingFailed;
  +
  +    /**
  +     * The actions that can modify objects in the repository. These are used
  +     * to determine actions cache invalidation.
  +     */
  +    private Set modificationActions;
  +    
       // ------------------------------------------------------- Security Methods
       
       
  @@ -448,6 +462,7 @@
                                   ActionNode action)
           throws ServiceAccessException, AccessDeniedException,
           ObjectNotFoundException {
  +        determineActionsCacheInvalidation(object, action);
           
           if (!hasPermission(object, subject, action)) {
               throw new AccessDeniedException(object.getUri(), subject.getUri(),
  @@ -469,6 +484,7 @@
        * @throws   ObjectNotFoundException
        */
       public void checkPermission(SlideToken token, ObjectNode object, ActionNode action) throws ServiceAccessException, AccessDeniedException, ObjectNotFoundException {
  +        determineActionsCacheInvalidation(object, action);
           
           if (!hasPermission(token, object, action)) {
               throw new AccessDeniedException(object.getUri(), getPrincipal(token).getUri(),
  @@ -1049,67 +1065,6 @@
           }
       }
       
  -    private void loadActionsCache(Namespace namespace, NamespaceConfig namespaceConfig) {
  -        try {
  -            actionAggregation = new HashMap();
  -            actionAggregationClosure = new HashMap();
  -            String actionsPath = namespaceConfig.getActionsPath();
  -            Uri actionsPathUri = namespace.getUri(actionsPath);
  -            ObjectNode actionsPathNode = actionsPathUri.getStore().retrieveObject(actionsPathUri);
  -            Enumeration actions = actionsPathNode.enumerateChildren();
  -            while (actions.hasMoreElements()) {
  -                ActionNode aNode = ActionNode.getActionNode((String)actions.nextElement());
  -                Set directAggregates = getActionAggregates(aNode);
  -                actionAggregation.put(aNode, directAggregates);
  -                Set aClosure = new HashSet();
  -                aClosure.add(aNode);
  -                aClosure.addAll(directAggregates);
  -                actionAggregationClosure.put(aNode, aClosure);
  -            }
  -            Iterator keys = actionAggregationClosure.keySet().iterator();
  -            while (keys.hasNext()) {
  -                ActionNode aNode = (ActionNode)keys.next();
  -                Set aClosure = (Set)actionAggregationClosure.get(aNode);
  -                actionAggregationClosure.put(aNode, buildClosure(aClosure));
  -            }
  -            // log success
  -            if (logger.isEnabled(LOG_CHANNEL, Logger.DEBUG)) {
  -                logger.log("Action aggregations loaded successfully", LOG_CHANNEL, Logger.DEBUG);
  -            }
  -            if (logger.isEnabled(LOG_CHANNEL, Logger.DEBUG)) {
  -                logger.log("\n@@@ Actions aggregations", LOG_CHANNEL, Logger.DEBUG);
  -                Iterator i = actionAggregation.entrySet().iterator();
  -                while (i.hasNext()) {
  -                    logger.log("  "+i.next(), LOG_CHANNEL, Logger.DEBUG);
  -                }
  -                logger.log("\n@@@ Action aggregations (transitive closure)", LOG_CHANNEL, Logger.DEBUG);
  -                i = actionAggregationClosure.entrySet().iterator();
  -                while (i.hasNext()) {
  -                    logger.log("  "+i.next(), LOG_CHANNEL, Logger.DEBUG);
  -                }
  -            }
  -        }
  -        catch (Throwable e) {
  -            actionAggregation = null;
  -            actionAggregationClosure = null;
  -        }
  -    }
  -    
  -    private Set buildClosure(Set aClosure) {
  -        Set result = new HashSet(aClosure);
  -        int size = 0;
  -        while (result.size() > size) {
  -            size = result.size();
  -            Set newResult = new HashSet();
  -            Iterator i = result.iterator();
  -            while (i.hasNext()) {
  -                newResult.addAll((Set)actionAggregationClosure.get(i.next()));
  -            }
  -            result = newResult;
  -        }
  -        return result;
  -    }
  -    
       /**
        * Get the direct aggregates
        *
  @@ -1131,9 +1086,20 @@
               else {
                   membersVal = new XMLValue((String)membersProp.getValue());
               }
  +            logger.log(membersVal.getHrefStrings(), LOG_CHANNEL, Logger.DEBUG);
               Iterator mUris = membersVal.getHrefStrings().iterator();
               while (mUris.hasNext()) {
  -                result.add(ActionNode.getActionNode((String)mUris.next()));
  +                String uriAsString = (String)mUris.next();
  +                Uri uri = new Uri(namespace, uriAsString);
  +                NodeRevisionNumber latestRevisionNumber = aNodeUri.getStore().retrieveRevisionDescriptors(uri).getLatestRevision();
  +                NodeProperty privilegeNamespace = aNodeUri.getStore().retrieveRevisionDescriptor(uri, latestRevisionNumber).getProperty(PRIVILEGE_NAMESPACE, "DAV:");
  +                org.jdom.Namespace namespace = null;
  +                if (privilegeNamespace != null && privilegeNamespace.getValue() instanceof String) {
  +                    namespace = org.jdom.Namespace.getNamespace((String) privilegeNamespace.getValue());
  +                } else {
  +                    namespace = org.jdom.Namespace.getNamespace("DAV:");
  +                }
  +                result.add(ActionNode.getActionNode(uriAsString, namespace));
               }
           }
           return result;
  @@ -1150,27 +1116,18 @@
        *
        */
       public boolean matchAction(SlideToken token, ActionNode checkAction, ActionNode permAction) throws ServiceAccessException {
  -        if (permAction == ActionNode.ALL) {
  +        if (permAction.equals(ActionNode.ALL)) {
               return true;
           }
  -        else {
  -            if (actionAggregationClosure != null) {
  -                Set permActionSet = (Set)actionAggregationClosure.get(permAction);
  -                if (permActionSet == null) {
  -                    logger.log("Unknown action " + permAction.getUri() , LOG_CHANNEL, Logger.WARNING);
  -                    return false;
  -                } else {    
  -                    return permActionSet.contains(checkAction);
  -                }
  -            }
  -            else {
  -                Uri u = namespace.getUri(token, checkAction.getUri());
  -                Store s = u.getStore();
  -                throw new ServiceAccessException(s, "Actions cache not loaded");
  -            }
  +        Map actionAggregationClosure = getActionAggregationClosureImpl(token, checkAction.getUri());
  +        Set permActionSet = (Set)actionAggregationClosure.get(permAction);
  +        if (permActionSet == null) {
  +            logger.log("Unknown action " + permAction.getUri() , LOG_CHANNEL, Logger.WARNING);
  +            return false;
           }
  +        return permActionSet.contains(checkAction);
       }
  -    
  +
       /**
        * Return true, if-and-only-if checkSubject matches permSubject.
        *
  @@ -1265,6 +1222,235 @@
        * @return   a Map: actionNode -> Set-of-aggregated-nodes (direct aggregates)
        */
       public Map getActionAggregation() {
  -        return Collections.unmodifiableMap(actionAggregation);
  +        logger.log("Action aggregation being retrieved", LOG_CHANNEL, Logger.DEBUG);
  +        return Collections.unmodifiableMap(getActionAggregationImpl());
  +    }
  +
  +    /**
  +     * Get the actions that modify objects in the repository. Which actions
  +     * these are is defined in the configuration.
  +     * 
  +     * @return The actions that modify objects in the repository.
  +     */
  +    private synchronized Set getModificationActions() {
  +        if (this.modificationActions == null) {
  +            Set modificationActions = new HashSet();
  +            modificationActions.add(namespaceConfig.getBindMemberAction());
  +            modificationActions.add(namespaceConfig.getCreateObjectAction());
  +            modificationActions.add(namespaceConfig.getCreateRevisionContentAction());
  +            modificationActions.add(namespaceConfig.getCreateRevisionMetadataAction());
  +            modificationActions.add(namespaceConfig.getModifyRevisionContentAction());
  +            modificationActions.add(namespaceConfig.getModifyRevisionMetadataAction());
  +            modificationActions.add(namespaceConfig.getRemoveObjectAction());
  +            modificationActions.add(namespaceConfig.getRemoveRevisionContentAction());
  +            modificationActions.add(namespaceConfig.getRemoveRevisionMetadataAction());
  +            modificationActions.add(namespaceConfig.getUnbindMemberAction());
  +            
  +            modificationActions.remove(namespaceConfig.getDefaultAction());
  +            
  +            this.modificationActions = Collections.unmodifiableSet(modificationActions);
  +        }
  +        return this.modificationActions;
  +    }
  +    
  +    /**
  +     * Get whether an acion can modify objects in the repository.
  +     * 
  +     * @param action The action for which to check whether it can modify
  +     *               objects in the repository.
  +     * @return True if the actions can modify objects in the repository,
  +     *         false otherwise.
  +     */
  +    private boolean isModificationAction(ActionNode action) {
  +        return getModificationActions().contains(action);
  +    }
  +    
  +    /**
  +     * Determine whether an operation on an object in the repository
  +     * invalidates the actions cache.
  +     * 
  +     * @param object The subject of the operation.
  +     * @param action The operation.
  +     */
  +    private void determineActionsCacheInvalidation(ObjectNode object, ActionNode action) {
  +        if (object.getUri().startsWith(namespaceConfig.getActionsPath())) {
  +            if (isModificationAction(action)) {
  +                if (logger.isEnabled(Logger.DEBUG)) {
  +                    logger.log("Actions cache invalidated for operation " + action.getUri()
  +                            + " on action " + object.getUri(), LOG_CHANNEL, Logger.DEBUG);
  +                }
  +                invalidateActionsCache();
  +            }
  +        }
  +    }
  +
  +    /**
  +     * Invalidates the actions cache causing it to be reloaded the next time it
  +     * is needed.
  +     */
  +    private synchronized void invalidateActionsCache() {
  +        hasActionsCacheLoadingFailed = false;
  +        actionAggregation = null;
  +        actionAggregationClosure = null;
  +    }
  +
  +    /**
  +     * Populate the actions cache.
  +     * 
  +     * @param namespace
  +     * @param namespaceConfig
  +     */
  +    private synchronized void loadActionsCache(Namespace namespace, NamespaceConfig namespaceConfig) {
  +        try {
  +            actionAggregation = new HashMap();
  +            actionAggregationClosure = new HashMap();
  +            String actionsPath = namespaceConfig.getActionsPath();
  +            Uri actionsPathUri = namespace.getUri(actionsPath);
  +            ObjectNode actionsPathNode = actionsPathUri.getStore().retrieveObject(actionsPathUri);
  +            Enumeration actions = actionsPathNode.enumerateChildren();
  +            addActionLeafsToActionAggregation(actions);
  +
  +            Iterator keys = actionAggregationClosure.keySet().iterator();
  +            while (keys.hasNext()) {
  +                ActionNode aNode = (ActionNode)keys.next();
  +                Set aClosure = (Set)actionAggregationClosure.get(aNode);
  +                actionAggregationClosure.put(aNode, buildClosure(aClosure));
  +            }
  +            // log success
  +            if (logger.isEnabled(LOG_CHANNEL, Logger.DEBUG)) {
  +                logger.log("Action aggregations loaded successfully", LOG_CHANNEL, Logger.DEBUG);
  +            }
  +            if (logger.isEnabled(LOG_CHANNEL, Logger.DEBUG)) {
  +                logger.log("\n@@@ Actions aggregations", LOG_CHANNEL, Logger.DEBUG);
  +                Iterator i = actionAggregation.entrySet().iterator();
  +                while (i.hasNext()) {
  +                    logger.log("  "+i.next(), LOG_CHANNEL, Logger.DEBUG);
  +                }
  +                logger.log("\n@@@ Action aggregations (transitive closure)", LOG_CHANNEL, Logger.DEBUG);
  +                i = actionAggregationClosure.entrySet().iterator();
  +                while (i.hasNext()) {
  +                    logger.log("  "+i.next(), LOG_CHANNEL, Logger.DEBUG);
  +                }
  +            }
  +        }
  +        catch (Throwable e) {
  +            logger.log(e, LOG_CHANNEL, Logger.DEBUG);
  +            actionAggregation = null;
  +            actionAggregationClosure = null;
  +            hasActionsCacheLoadingFailed = true;
  +        }
  +    }
  +    
  +    /**
  +     * Add children of <code>actions</code> which are leafs, collections
  +     * without children, to the action cache. Recursively invoke this method
  +     * for each child which is not a leaf.
  +     * 
  +     * @param actions The collection containing child nodes.
  +     * @throws SlideException
  +     * @throws JDOMException
  +     */
  +    private synchronized void addActionLeafsToActionAggregation(Enumeration actions) throws SlideException, JDOMException {
  +        while (actions.hasMoreElements()) {
  +            Uri aNodeUri = namespace.getUri((String)actions.nextElement());
  +            ObjectNode oNode = namespace.getStore(aNodeUri.getScope()).retrieveObject(aNodeUri);
  +            if (oNode.hasChildren()) {
  +                if (logger.isEnabled(Logger.DEBUG)) {
  +                    logger.log("Adding children of action " + oNode.getUri() + " to action aggregation", LOG_CHANNEL, Logger.DEBUG);
  +                }
  +                addActionLeafsToActionAggregation(oNode.enumerateChildren());
  +            } else {
  +                if (logger.isEnabled(Logger.DEBUG)) {
  +                    logger.log("Adding action " + oNode.getUri() + " to action aggregation", LOG_CHANNEL, Logger.DEBUG);
  +                }
  +                NodeRevisionNumber latestRevisionNumber = namespace.getStore(aNodeUri.getScope()).retrieveRevisionDescriptors(aNodeUri).getLatestRevision();
  +                NodeProperty privilegeNamespace = namespace.getStore(aNodeUri.getScope()).retrieveRevisionDescriptor(aNodeUri, latestRevisionNumber).getProperty(PRIVILEGE_NAMESPACE, "DAV:");
  +                ActionNode aNode;
  +                org.jdom.Namespace actionNamespace;
  +                if (privilegeNamespace != null && privilegeNamespace.getValue() instanceof String) {
  +                    actionNamespace = org.jdom.Namespace.getNamespace((String) privilegeNamespace.getValue());
  +                } else {
  +                    actionNamespace = org.jdom.Namespace.getNamespace("DAV:");
  +                }
  +                aNode = ActionNode.getActionNode(oNode.getUri(), actionNamespace);
  +                Set directAggregates = getActionAggregates(aNode);
  +                actionAggregation.put(aNode, directAggregates);
  +                Set aClosure = new HashSet();
  +                aClosure.add(aNode);
  +                aClosure.addAll(directAggregates);
  +                actionAggregationClosure.put(aNode, aClosure);
  +            }
  +        }
  +    }
  +    
  +    /**
  +     * Determines the complete set of children and grandchildren of an action.
  +     * These are the children/grandchildren determined by the
  +     * <code>D:privilege-member-set</code> hierarchy.
  +     * 
  +     * @param aClosure A collection containing the action and its known
  +     *                 children and grandchildren.
  +     * @return A collection containing the action, its known and additionally
  +     *         found children and grandchildren.
  +     */
  +    private synchronized Set buildClosure(Set aClosure) {
  +        Set result = new HashSet(aClosure);
  +        int size = 0;
  +        while (result.size() > size) {
  +            size = result.size();
  +            Set newResult = new HashSet();
  +            Iterator i = result.iterator();
  +            while (i.hasNext()) {
  +                Object member = i.next();
  +                Set membersOfMember = (Set)actionAggregationClosure.get(member);
  +                if (membersOfMember != null) {
  +                    newResult.addAll(membersOfMember);
  +                }
  +            }
  +            result = newResult;
  +        }
  +        return result;
  +    }
  +    
  +    /**
  +     * Get a map which maps an action to its direct aggregated actions.
  +     * 
  +     * If the actions cache is not loaded, an attempt will be made to load the
  +     * actions cache.
  +     * 
  +     * @return A map from actions to their aggregated actions, or and empty map
  +     *         if loading of the actions cache failed.
  +     */
  +    private synchronized Map getActionAggregationImpl() {
  +        if (this.actionAggregation == null && !this.hasActionsCacheLoadingFailed) {
  +            loadActionsCache(this.namespace, this.namespaceConfig);
  +        }
  +        if (this.hasActionsCacheLoadingFailed) {
  +            logger.log("actionAggregation retrieved but cache didn't load successfully" , LOG_CHANNEL, Logger.WARNING);
  +            return new HashMap();
  +        }
  +        return this.actionAggregation;
  +    }
  +    
  +    /**
  +     * Get a map which maps an action to all its aggregated actions.
  +     * 
  +     * If the actions cache is not loaded, an attempt will be made to load the
  +     * actions cache.
  +     * 
  +     * @return A map from actions to their aggregated actions.
  +     * @throws ServiceAccessException Indicates the loading of the actions
  +     *                                cache failed.
  +     */
  +    private synchronized Map getActionAggregationClosureImpl(SlideToken token, String uri) throws ServiceAccessException {
  +        if (this.actionAggregationClosure == null && !this.hasActionsCacheLoadingFailed) {
  +            loadActionsCache(this.namespace, this.namespaceConfig);
  +        }
  +        if (this.hasActionsCacheLoadingFailed) {
  +            Uri u = this.namespace.getUri(token, uri);
  +            Store s = u.getStore();
  +            throw new ServiceAccessException(s, "Actions cache not loaded");
  +        }
  +        return this.actionAggregationClosure;
       }
   }
  
  
  
  1.45      +59 -12    jakarta-slide/src/webdav/server/org/apache/slide/webdav/method/AclMethod.java
  
  Index: AclMethod.java
  ===================================================================
  RCS file: /home/cvs/jakarta-slide/src/webdav/server/org/apache/slide/webdav/method/AclMethod.java,v
  retrieving revision 1.44
  retrieving revision 1.45
  diff -u -r1.44 -r1.45
  --- AclMethod.java	3 Aug 2004 09:37:48 -0000	1.44
  +++ AclMethod.java	9 Aug 2004 12:05:29 -0000	1.45
  @@ -28,12 +28,16 @@
   import java.util.Iterator;
   import java.util.List;
   import java.util.Vector;
  -
   import org.apache.slide.common.NamespaceAccessToken;
   import org.apache.slide.common.ServiceAccessException;
  -import org.apache.slide.event.EventDispatcher;
  +import org.apache.slide.common.SlideException;
  +import org.apache.slide.common.Uri;
  +import org.apache.slide.content.NodeProperty;
  +import org.apache.slide.content.NodeRevisionDescriptor;
  +import org.apache.slide.content.NodeRevisionDescriptors;
   import org.apache.slide.security.NodePermission;
   import org.apache.slide.structure.ActionNode;
  +import org.apache.slide.structure.ObjectNode;
   import org.apache.slide.structure.SubjectNode;
   import org.apache.slide.webdav.WebdavException;
   import org.apache.slide.webdav.WebdavServletConfig;
  @@ -41,14 +45,15 @@
   import org.apache.slide.webdav.util.AclConstants;
   import org.apache.slide.webdav.util.PreconditionViolationException;
   import org.apache.slide.webdav.util.ViolatedPrecondition;
  +import org.apache.slide.webdav.util.WebdavConstants;
   import org.apache.slide.webdav.util.WebdavStatus;
  +import org.apache.slide.event.EventDispatcher;
   import org.jdom.Element;
   import org.jdom.JDOMException;
   
   
   /**
    * ACL method.
  - *
    */
   public class AclMethod extends AbstractWebdavMethod implements AclConstants, WriteMethod {
       
  @@ -102,9 +107,14 @@
               } catch (IOException x) {}
               throw new WebdavException(e.getStatusCode());
           }
  +        catch (SlideException e) {
  +            int statusCode = WebdavStatus.SC_INTERNAL_SERVER_ERROR;
  +            sendError( statusCode, e );
  +            throw new WebdavException( statusCode );
  +        }
       }
       
  -    private List createNodePermissionList( Element aceElm ) throws JDOMException, PreconditionViolationException {
  +    private List createNodePermissionList( Element aceElm ) throws PreconditionViolationException, SlideException, JDOMException {
           List result = new ArrayList();
           String objectUri = resourcePath;
           String subjectUri = null;
  @@ -206,17 +216,55 @@
           );
       }
       
  -    private String createActionUri(Element privilegeElm) throws JDOMException, PreconditionViolationException {
  -        String privilege = ((Element)privilegeElm.getChildren().get(0)).getName();
  -        if (E_ALL.equals(privilege)) {
  +    private String createActionUri(Element privilegeElm) throws JDOMException, PreconditionViolationException, SlideException {
  +        Element privilegeChildElm = (Element)privilegeElm.getChildren().get(0);
  +        String privilegeName = privilegeChildElm.getName();
  +        String privilegeNamespace = privilegeChildElm.getNamespaceURI();
  +        if (E_ALL.equals(privilegeName) && S_DAV.equals(privilegeNamespace)) {
               return ActionNode.ALL_URI;
           }
           else {
  -            return token.getNamespaceConfig().getActionsPath()+"/"+privilege;
  +            ObjectNode actions = structure.retrieve(slideToken, token.getNamespaceConfig().getActionsPath());
  +            ObjectNode action = findAction(actions.getChildren().iterator(), privilegeName, privilegeNamespace);
  +            return action.getUri();
           }
       }
       
       /**
  +     * Find an action in the repository based on its simple name and its namespace.
  +     * 
  +     * @param actionsIterator An iterator over the actions collection to search in.
  +     * @param privilegeName The simple name of the action.
  +     * @param privilegeNamespace The name space of the action.
  +     * @return The action having the simple name and namespace.
  +     * @throws SlideException There was a problem accessing the actions in the repository.
  +     */
  +    private ObjectNode findAction(Iterator actionsIterator, String privilegeName, String privilegeNamespace) throws SlideException {
  +        ObjectNode result = null;
  +        while (result == null && actionsIterator.hasNext()) {
  +            String aUriAsString = (String) actionsIterator.next();
  +            ObjectNode aNode = structure.retrieve(slideToken, aUriAsString);
  +            if (aNode.hasChildren()) {
  +                result = findAction(aNode.getChildren().iterator(), privilegeName, privilegeNamespace);
  +            }
  +            else {
  +                Uri aUri = token.getUri(slideToken, aUriAsString);
  +                NodeRevisionDescriptors nrds = aUri.getStore().retrieveRevisionDescriptors(aUri);
  +                NodeRevisionDescriptor latestNrd = aUri.getStore().retrieveRevisionDescriptor(aUri, nrds.getLatestRevision());
  +                NodeProperty aNamespaceAsNode = latestNrd.getProperty(AclConstants.P_PRIVILEGE_NAMESPACE, WebdavConstants.S_DAV);
  +                String aNamespace = aNamespaceAsNode == null ? null : aNamespaceAsNode.getValue().toString();
  +                String aUriLastSegment = aUriAsString.substring(aUriAsString.lastIndexOf('/') + 1);
  +                if (aUriLastSegment.equals(privilegeName)
  +                        && ((aNamespace != null && privilegeNamespace.equals(aNamespace))
  +                                || (aNamespace == null && privilegeNamespace.equals(WebdavConstants.S_DAV)))) {
  +                    result = aNode;
  +                }
  +            }
  +        }
  +        return result;
  +    }
  +
  +    /**
        * Method checkPreconditions
        *
        * @throws   PreconditionViolationException
  @@ -267,4 +315,3 @@
           }
       }
   }
  -
  
  
  
  1.10      +74 -14    jakarta-slide/src/share/org/apache/slide/structure/ActionNode.java
  
  Index: ActionNode.java
  ===================================================================
  RCS file: /home/cvs/jakarta-slide/src/share/org/apache/slide/structure/ActionNode.java,v
  retrieving revision 1.9
  retrieving revision 1.10
  diff -u -r1.9 -r1.10
  --- ActionNode.java	28 Jul 2004 09:34:35 -0000	1.9
  +++ ActionNode.java	9 Aug 2004 12:05:29 -0000	1.10
  @@ -24,10 +24,18 @@
   package org.apache.slide.structure;
   
   import java.util.Vector;
  +import org.apache.slide.content.NodeProperty.NamespaceCache;
  +import org.jdom.Namespace;
   
   /**
  - * Action node class.
  - *
  + * Action node class. The namespace is used to distinguish actions with the
  + * same simple name.
  + * 
  + * Although this class has a <code>namespace</code> attribute the equality of
  + * two <code>ActionNode</code>s can still be determined by comparing their
  + * URIs. Therefore this class does not override <code>equals(Object)</code>
  + * from <code>ObjectNode</code>.
  + * 
    * @version $Revision$
    */
   public class ActionNode extends ObjectNode {
  @@ -36,8 +44,13 @@
       public static final String DEFAULT_URI = "default";
       public static final String ALL_URI = "all";
   
  -    public static final ActionNode DEFAULT = new ActionNode(DEFAULT_URI);
  -    public static final ActionNode ALL = new ActionNode(ALL_URI);
  +    public static final ActionNode DEFAULT = new ActionNode(DEFAULT_URI, NamespaceCache.DEFAULT_NAMESPACE);
  +    public static final ActionNode ALL = new ActionNode(ALL_URI, NamespaceCache.DEFAULT_NAMESPACE);
  +    
  +    /**
  +     * The namespace of the action.
  +     */
  +    private Namespace namespace;
       
       // ----------------------------------------------------------- Constructors
       
  @@ -67,17 +80,64 @@
           super(uuri, bindings, parentBindings, links);
       }
       
  -    public static String getActionUri(String actionUri) {
  -        if (ActionNode.ALL_URI != actionUri && ActionNode.ALL_URI.equals(actionUri)) {
  -            return ActionNode.ALL_URI;
  -        }
  -        return actionUri;
  +    /**
  +     * Create an <code>ActionNode</code> with a namespace extracted from the
  +     * <code>privilege-namespace</code> property.
  +     * 
  +     * @param uri The Slide-internal URI of the ActionNode.
  +     * @param namespace The namespace of the action.
  +     */
  +    public ActionNode(String uri, Namespace namespace) {
  +        super(uri);
  +        this.namespace = namespace;
       }
  -    
  +
  +    /**
  +     * Create an <code>ActionNode</code> without a namespace. If retrieval of
  +     * the namespace is attempted on the result an exception will be thrown.
  +     * 
  +     * This method is not required to return a unique instance each time it is
  +     * invoked.
  +     * 
  +     * @param actionUri The URI which uniquely identifies the <code>ActionNode</code>.
  +     * @return An <code>ActionNode</code> without a namespace.
  +     */
       public static ActionNode getActionNode(String actionUri) {
  -        if (ActionNode.ALL_URI == actionUri || ActionNode.ALL_URI.equals(actionUri)) {
  +        if (ActionNode.ALL_URI.equals(actionUri)) {
               return ActionNode.ALL;
           }
           return new ActionNode(actionUri);
  +    }
  +    
  +    /**
  +     * Create an ActionNode with a namespace.
  +     * 
  +     * This method is not required to return a unique instance each time it is
  +     * invoked.
  +     * 
  +     * @param The URI which uniquely identifies the <code>ActionNode</code>.
  +     * @param namespace The namespace of the <code>ActionNode</code>.
  +     * @return An <code>ActionNode</code> with a namespace.
  +     */
  +    public static ActionNode getActionNode(String actionUri, Namespace namespace) {
  +        if (ActionNode.ALL_URI.equals(actionUri) && NamespaceCache.DEFAULT_NAMESPACE.equals(namespace)) {
  +            return ActionNode.ALL;
  +        }
  +        return new ActionNode(actionUri, namespace);
  +    }
  +
  +    /**
  +     * Get the namespace. If the <code>ActionNode</code> was constructed
  +     * without a namespace, an exception will be thrown.
  +     * 
  +     * @return The namespace of the <code>ActionNode</code>.
  +     * @throws IllegalStateException The <code>ActionNode</code> was
  +     *                               constructed without a namespace.
  +     */
  +    public Namespace getNamespace() {
  +        if (this.namespace == null) {
  +            throw new IllegalStateException("Namespace retrieved without being specified");
  +        }
  +        return this.namespace;
       }
   }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: slide-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: slide-dev-help@jakarta.apache.org