You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by an...@apache.org on 2009/01/28 10:50:51 UTC

svn commit: r738422 [2/3] - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/ main/java/org/apache/jackrabbit/core/lock/ main/java/org/apache/jackrabbit/core/retention/ main/java/org/apache/jackrabbit/core/security/authori...

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionManagerImpl.java?rev=738422&r1=738421&r2=738422&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionManagerImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionManagerImpl.java Wed Jan 28 09:50:50 2009
@@ -16,69 +16,193 @@
  */
 package org.apache.jackrabbit.core.retention;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.jackrabbit.api.jsr283.retention.RetentionManager;
 import org.apache.jackrabbit.api.jsr283.retention.Hold;
+import org.apache.jackrabbit.api.jsr283.retention.RetentionManager;
 import org.apache.jackrabbit.api.jsr283.retention.RetentionPolicy;
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.ProtectedItemModifier;
+import org.apache.jackrabbit.core.PropertyImpl;
+import org.apache.jackrabbit.core.security.authorization.Permission;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.NameFactory;
+import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import javax.jcr.PathNotFoundException;
 import javax.jcr.AccessDeniedException;
+import javax.jcr.PathNotFoundException;
 import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.version.VersionException;
+import javax.jcr.Value;
+import javax.jcr.PropertyType;
 import javax.jcr.lock.LockException;
+import javax.jcr.version.VersionException;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * <code>RetentionManagerImpl</code>...
  */
-public class RetentionManagerImpl implements RetentionManager {
+public class RetentionManagerImpl extends ProtectedItemModifier implements RetentionManager {
 
     private static Logger log = LoggerFactory.getLogger(RetentionManagerImpl.class);
 
-    private final Session session;
-    
-    public RetentionManagerImpl(Session session) {
+    private static final NameFactory NAME_FACTORY = NameFactoryImpl.getInstance();
+
+    static final Name REP_RETENTION_MANAGEABLE = NAME_FACTORY.create(Name.NS_REP_URI, "RetentionManageable");
+    static final Name REP_HOLD = NAME_FACTORY.create(Name.NS_REP_URI, "hold");
+    static final Name REP_RETENTION_POLICY = NAME_FACTORY.create(Name.NS_REP_URI, "retentionPolicy");
+
+    private final SessionImpl session;
+
+    /**
+     *
+     * @param session The editing session.
+     */
+    public RetentionManagerImpl(SessionImpl session) {
+        super(Permission.RETENTION_MNGMT);
         this.session = session;
     }
-
+    
+    //---------------------------------------------------< RetentionManager >---
+    /**
+     * @see RetentionManager#getHolds(String)
+     */
     public Hold[] getHolds(String absPath) throws PathNotFoundException,
             AccessDeniedException, RepositoryException {
-        //TODO
-        return new Hold[0];
+
+        NodeImpl n = (NodeImpl) session.getNode(absPath);
+        session.getAccessManager().checkPermission(session.getQPath(absPath), Permission.RETENTION_MNGMT);        
+
+        Hold[] holds;
+        if (n.isNodeType(REP_RETENTION_MANAGEABLE) && n.hasProperty(REP_HOLD)) {
+            holds = HoldImpl.createFromProperty(n.getProperty(REP_HOLD), n.getNodeId());
+        } else {
+            holds = new Hold[0];
+        }
+        return holds;
     }
 
+    /**
+     * @see RetentionManager#addHold(String, String, boolean) 
+     */
     public Hold addHold(String absPath, String name, boolean isDeep) throws
             PathNotFoundException, AccessDeniedException, LockException,
             VersionException, RepositoryException {
-        //TODO
-        throw new UnsupportedOperationException("Not yet implemented");
-    }
 
+        NodeImpl n = (NodeImpl) session.getNode(absPath);
+        if (!n.isNodeType(REP_RETENTION_MANAGEABLE)) {
+            n.addMixin(REP_RETENTION_MANAGEABLE);
+        }
+
+        HoldImpl hold = new HoldImpl(session.getQName(name), isDeep, n.getNodeId(), session);
+        Value[] vls;
+        if (n.hasProperty(REP_HOLD)) {
+            Value[] vs = n.getProperty(REP_HOLD).getValues();
+            // check if the same hold already exists
+            for (int i = 0; i < vs.length; i++) {
+                if (hold.equals(HoldImpl.createFromValue(vs[i], n.getNodeId(), session))) {
+                    throw new RepositoryException("Hold already exists.");
+                }
+            }
+            vls = new Value[vs.length + 1];
+            System.arraycopy(vs, 0, vls, 0, vs.length);
+        } else {
+            vls = new Value[1];
+        }
+
+        // add the value of the new hold
+        vls[vls.length - 1] = hold.toValue(session.getValueFactory());
+        setProperty(n, REP_HOLD, vls);
+        return hold;
+    }
+
+    /**
+     * @see RetentionManager#removeHold(String, Hold) 
+     */
     public void removeHold(String absPath, Hold hold) throws
             PathNotFoundException, AccessDeniedException, LockException,
             VersionException, RepositoryException {
-        //TODO
-        throw new UnsupportedOperationException("Not yet implemented");
-    }
 
+        NodeImpl n = (NodeImpl) session.getNode(absPath);
+        if (hold instanceof HoldImpl
+                && n.getNodeId().equals(((HoldImpl) hold).getNodeId())
+                && n.isNodeType(REP_RETENTION_MANAGEABLE)
+                && n.hasProperty(REP_HOLD)) {
+
+            PropertyImpl p = n.getProperty(REP_HOLD);
+            Value[] vls = p.getValues();
+
+            List newValues = new ArrayList(vls.length - 1);
+            for (int i = 0; i < vls.length; i++) {
+                if (!hold.equals(HoldImpl.createFromValue(vls[i], n.getNodeId(), session))) {
+                    newValues.add(vls[i]);
+                }
+            }
+            if (newValues.size() < vls.length) {
+                if (newValues.size() == 0) {
+                    removeItem(p);
+                } else {
+                    setProperty(n, REP_HOLD, (Value[]) newValues.toArray(new Value[newValues.size()]));
+                }
+            } else {
+                // no matching hold.
+                throw new RepositoryException("Cannot remove '" + hold.getName() + "' at " + absPath + ".");
+            }
+        } else {
+            // invalid hold or no hold at absPath
+            throw new RepositoryException("Cannot remove '" + hold.getName() + "' at " + absPath + ".");
+        }
+    }
+
+    /**
+     * @see RetentionManager#getRetentionPolicy(String) 
+     */
     public RetentionPolicy getRetentionPolicy(String absPath) throws
             PathNotFoundException, AccessDeniedException, RepositoryException {
-        // TODO
-        return null;
+
+        NodeImpl n = (NodeImpl) session.getNode(absPath);
+        session.getAccessManager().checkPermission(session.getQPath(absPath), Permission.RETENTION_MNGMT);
+
+        RetentionPolicy rPolicy = null;
+        if (n.isNodeType(REP_RETENTION_MANAGEABLE) && n.hasProperty(REP_RETENTION_POLICY)) {
+            String jcrName = n.getProperty(REP_RETENTION_POLICY).getString();
+            rPolicy = new RetentionPolicyImpl(jcrName, n.getNodeId(), session);
+        }
+        
+        return rPolicy;
     }
 
+    /**
+     * @see RetentionManager#setRetentionPolicy(String, RetentionPolicy)
+     */
     public void setRetentionPolicy(String absPath, RetentionPolicy retentionPolicy)
             throws PathNotFoundException, AccessDeniedException, LockException,
             VersionException, RepositoryException {
-        //TODO
-        throw new UnsupportedOperationException("Not yet implemented");
+
+        NodeImpl n = (NodeImpl) session.getNode(absPath);
+        if (!(retentionPolicy instanceof RetentionPolicyImpl)) {
+            throw new RepositoryException("Invalid retention policy.");
+        }
+        Value retentionReference = session.getValueFactory().createValue(retentionPolicy.getName(), PropertyType.NAME);
+        if (!n.isNodeType(REP_RETENTION_MANAGEABLE)) {
+            n.addMixin(REP_RETENTION_MANAGEABLE);
+        }
+        setProperty(n, REP_RETENTION_POLICY, retentionReference);
     }
 
+    /**
+     * @see RetentionManager#removeRetentionPolicy(String) 
+     */
     public void removeRetentionPolicy(String absPath) throws
             PathNotFoundException, AccessDeniedException, LockException,
             VersionException, RepositoryException {
-        //TODO
-        throw new UnsupportedOperationException("Not yet implemented");
+
+        NodeImpl n = (NodeImpl) session.getNode(absPath);
+        if (n.isNodeType(REP_RETENTION_MANAGEABLE) && n.hasProperty(REP_RETENTION_POLICY)) {
+            removeItem(n.getProperty(REP_RETENTION_POLICY));
+        } else {
+            throw new RepositoryException("Cannot remove retention policy at absPath.");
+        }
     }
 }
\ No newline at end of file

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionPolicyImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionPolicyImpl.java?rev=738422&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionPolicyImpl.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionPolicyImpl.java Wed Jan 28 09:50:50 2009
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.retention;
+
+import org.apache.jackrabbit.api.jsr283.retention.RetentionPolicy;
+import org.apache.jackrabbit.core.NodeId;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
+import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.NamespaceException;
+
+/**
+ * Basic implementation of the <code>RetentionPolicy</code> interface.
+ */
+public class RetentionPolicyImpl implements RetentionPolicy {
+
+    private final Name name;
+    private final NodeId nodeId;
+    private final NameResolver resolver;
+
+    private int hashCode = 0;
+    
+    public RetentionPolicyImpl(String jcrName, NameResolver resolver) throws IllegalNameException, NamespaceException {
+        this(resolver.getQName(jcrName), null, resolver);
+    }
+
+    RetentionPolicyImpl(String jcrName, NodeId nodeId, NameResolver resolver) throws IllegalNameException, NamespaceException {
+        this(resolver.getQName(jcrName), nodeId, resolver);
+    }
+
+    private RetentionPolicyImpl(Name name, NodeId nodeId, NameResolver resolver) {
+        this.name = name;
+        this.nodeId = nodeId;
+        this.resolver = resolver;
+    }
+
+    NodeId getNodeId() {
+        return nodeId;
+    }
+    
+    //----------------------------------------------------< RetentionPolicy >---
+    /**
+     * @see org.apache.jackrabbit.api.jsr283.retention.RetentionPolicy#getName()
+     */
+    public String getName() throws RepositoryException {
+        return resolver.getJCRName(name);
+    }
+
+    //-------------------------------------------------------------< Object >---
+    /**
+     * @see Object#hashCode()
+     */
+    public int hashCode() {
+        if (hashCode == 0) {
+            int h = 17;
+            h = 37 * h + name.hashCode();
+            h = 37 * h + nodeId.hashCode();
+            hashCode = h;
+        }
+        return hashCode;
+    }
+
+    /**
+     * @see Object#equals(Object)
+     */
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj instanceof RetentionPolicyImpl) {
+            RetentionPolicyImpl other = (RetentionPolicyImpl) obj;
+            return name.equals(other.name) && ((nodeId == null) ? other.nodeId == null : nodeId.equals(other.nodeId));
+        }
+        return false;
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionPolicyImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionPolicyImpl.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionRegistry.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionRegistry.java?rev=738422&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionRegistry.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionRegistry.java Wed Jan 28 09:50:50 2009
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.retention;
+
+import org.apache.jackrabbit.spi.Path;
+
+import javax.jcr.RepositoryException;
+
+/**
+ * <code>RetentionEvaluator</code>...
+ */
+public interface RetentionRegistry {
+
+    public boolean hasEffectiveHold(Path nodePath, boolean checkParent) throws RepositoryException;
+
+    public boolean hasEffectiveRetention(Path nodePath, boolean checkParent) throws RepositoryException;
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionRegistry.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionRegistry.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionRegistryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionRegistryImpl.java?rev=738422&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionRegistryImpl.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionRegistryImpl.java Wed Jan 28 09:50:50 2009
@@ -0,0 +1,349 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.retention;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.api.jsr283.retention.Hold;
+import org.apache.jackrabbit.api.jsr283.retention.RetentionPolicy;
+import org.apache.jackrabbit.core.NodeId;
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.PropertyId;
+import org.apache.jackrabbit.core.PropertyImpl;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.fs.FileSystem;
+import org.apache.jackrabbit.core.fs.FileSystemException;
+import org.apache.jackrabbit.core.fs.FileSystemResource;
+import org.apache.jackrabbit.core.observation.SynchronousEventListener;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.spi.commons.name.PathMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Workspace;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * <code>RetentionEvaluatorImpl</code>...
+ */
+public class RetentionRegistryImpl implements RetentionRegistry, SynchronousEventListener {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = LoggerFactory.getLogger(RetentionRegistryImpl.class);
+    /**
+     * Name of the file storing the existing retention/holds
+     */
+    private static final String FILE_NAME = "retention";
+
+    private final PathMap retentionMap  = new PathMap();
+    private final PathMap holdMap = new PathMap();
+
+    private final SessionImpl session;
+    private final FileSystemResource retentionFile;
+
+    private long holdCnt = 0;
+    private long retentionCnt = 0;
+    
+    private boolean initialized = false;
+
+    public RetentionRegistryImpl(SessionImpl session, FileSystem fs) throws RepositoryException {
+        this.session = session;
+        this.retentionFile = new FileSystemResource(fs, FileSystem.SEPARATOR + FILE_NAME);
+
+        // start listening to added/changed or removed holds and retention policies.
+        Workspace wsp = session.getWorkspace();
+        // register eventlistener to be informed about new/removed holds and retention plcs.
+        int types = Event.PROPERTY_ADDED | Event.PROPERTY_REMOVED | Event.PROPERTY_CHANGED;
+        String[] ntFilter = new String[] {session.getJCRName(RetentionManagerImpl.REP_RETENTION_MANAGEABLE)};
+        wsp.getObservationManager().addEventListener(this, types, "/", true, null, ntFilter, false);
+
+        // populate the retentionMap and the holdMap with the effective
+        // holds and retention plcs present within the content.
+        try {
+            readRetentionFile();
+        } catch (FileSystemException e) {
+            throw new RepositoryException("Error while reading retention/holds from '" + retentionFile.getPath() + "'", e);
+        } catch (IOException e) {
+            throw new RepositoryException("Error while reading retention/holds from '" + retentionFile.getPath() + "'", e);
+        }
+        initialized = true;
+    }
+
+    /**
+     * Read the file system resource containing the node ids of those nodes
+     * contain holds/retention policies and populate the 2 path maps.
+     *
+     * If an entry in the retention file doesn't have a corresponding entry
+     * (either rep:hold property or rep:retentionPolicy property at the
+     * node identified by the node id entry) or doesn't correspond to an existing
+     * node, that entry will be ignored. Upon {@link #close()} of this
+     * manager, the file will be updated to reflect the actual set of holds/
+     * retentions present and effective in the content.
+     * 
+     * @throws IOException
+     * @throws FileSystemException
+     */
+    private void readRetentionFile() throws IOException, FileSystemException {
+        if (retentionFile.exists()) {
+            BufferedReader reader = null;
+            try {
+                reader = new BufferedReader(new InputStreamReader(retentionFile.getInputStream()));
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    NodeId nodeId = NodeId.valueOf(line);
+                    try {
+                        NodeImpl node = (NodeImpl) session.getItemManager().getItem(nodeId);
+                        Path nodePath = node.getPrimaryPath();
+
+                        if (node.hasProperty(RetentionManagerImpl.REP_HOLD)) {
+                            PropertyImpl prop = node.getProperty(RetentionManagerImpl.REP_HOLD);
+                            addHolds(nodePath, prop);
+                        }
+                        if (node.hasProperty(RetentionManagerImpl.REP_RETENTION_POLICY)) {
+                            PropertyImpl prop = node.getProperty(RetentionManagerImpl.REP_RETENTION_POLICY);
+                            addRetentionPolicy(nodePath, prop);
+                        }
+                    } catch (RepositoryException e) {
+                        // node doesn't exist any more or hold/retention has been removed.
+                        // ignore. upon close() the file will not contain the given nodeId
+                        // any more.
+                        log.warn("Unable to read retention policy / holds from node '" + nodeId + "': " + e.getMessage());
+                    }
+                }
+            } finally {
+                IOUtils.closeQuietly(reader);
+            }
+        }
+    }
+
+    /**
+     * Write back the file system resource containing the node ids of those
+     * nodes containing holds and/or retention policies. Each node id is
+     * present only once.
+     */
+    private void writeRetentionFile() {
+        final Set nodeIds = new HashSet();
+
+        // first look for nodes containing holds
+        holdMap.traverse(new PathMap.ElementVisitor() {
+            public void elementVisited(PathMap.Element element) {
+                List holds = (List) element.get();
+                if (!holds.isEmpty()) {
+                    nodeIds.add(((HoldImpl) holds.get(0)).getNodeId());
+                }
+            }
+        }, false);
+
+        // then collect ids of nodes having an retention policy
+        retentionMap.traverse(new PathMap.ElementVisitor() {
+            public void elementVisited(PathMap.Element element) {
+                RetentionPolicyImpl rp  = (RetentionPolicyImpl) element.get();
+                nodeIds.add(rp.getNodeId());
+            }
+        }, false);
+
+        if (!nodeIds.isEmpty()) {
+            BufferedWriter writer = null;
+            try {
+                writer = new BufferedWriter(new OutputStreamWriter(retentionFile.getOutputStream()));
+                for (Iterator it = nodeIds.iterator(); it.hasNext();) {
+                    writer.write(it.next().toString());
+                    if (it.hasNext()) {
+                        writer.newLine();
+                    }
+                }
+            } catch (FileSystemException fse) {
+                log.error("Error while saving locks to '" + retentionFile.getPath() + "': " + fse.getMessage());
+            } catch (IOException ioe) {
+                log.error("Error while saving locks to '" + retentionFile.getPath() + "': " + ioe.getMessage());
+            } finally {
+                IOUtils.closeQuietly(writer);
+            }
+        }
+    }
+
+    public void close()  {
+        writeRetentionFile();
+        initialized = false;
+    }
+    
+    private void addHolds(Path nodePath, PropertyImpl p) throws RepositoryException {
+        synchronized (holdMap) {
+            Hold[] holds = HoldImpl.createFromProperty(p, ((PropertyId) p.getId()).getParentId());
+            holdMap.put(nodePath, Arrays.asList(holds));
+            holdCnt++;            
+        }
+    }
+
+    private void removeHolds(Path nodePath) {
+        synchronized (holdMap) {
+            PathMap.Element el = holdMap.map(nodePath, true);
+            if (el != null) {
+                el.remove();
+                holdCnt--;
+            } // else: no entry for holds on nodePath (should not occur)
+        }
+    }
+
+    private void addRetentionPolicy(Path nodePath, PropertyImpl p) throws RepositoryException {
+        synchronized (retentionMap) {
+            RetentionPolicy rp = new RetentionPolicyImpl(p.getString(), ((PropertyId) p.getId()).getParentId(), session);
+            retentionMap.put(nodePath, rp);
+            retentionCnt++;
+        }
+    }
+
+    private void removeRetentionPolicy(Path nodePath) {
+        synchronized (retentionMap) {
+            PathMap.Element el = retentionMap.map(nodePath, true);
+            if (el != null) {
+                el.remove();
+                retentionCnt--;
+            } // else: no entry for holds on nodePath (should not occur)
+        }
+    }
+
+    //--------------------------------------------------< RetentionRegistry >---
+    /**
+     * @see RetentionRegistry#hasEffectiveHold(org.apache.jackrabbit.spi.Path,boolean)
+     */
+    public boolean hasEffectiveHold(Path nodePath, boolean checkParent) throws RepositoryException {
+        if (!initialized) {
+            throw new IllegalStateException("Not initialized.");
+        }
+        if (holdCnt <= 0) {
+            return false;
+        }
+        PathMap.Element element = holdMap.map(nodePath, false);
+        List holds = (List) element.get();
+        if (holds != null) {
+            if (element.hasPath(nodePath)) {
+                // one or more holds on the specified path
+                return true;
+            } else if (checkParent && !nodePath.denotesRoot() &&
+                    element.hasPath(nodePath.getAncestor(1))) {
+                // hold present on the parent node whithout checking for being
+                // a deep hold.
+                // this required for removal of a node that can be inhibited
+                // by a hold on the node itself, by a hold on the parent or
+                // by a deep hold on any ancestor.
+                return true;
+            } else {
+                for (Iterator it = holds.iterator(); it.hasNext();) {
+                    Hold h = (Hold) it.next();
+                    if (h.isDeep()) {
+                        return true;
+                    }
+                }
+            }
+        }
+        // no hold at path or no deep hold on parent.
+        return false;
+    }
+
+    /**
+     * @see RetentionRegistry#hasEffectiveRetention(org.apache.jackrabbit.spi.Path,boolean)
+     */
+    public boolean hasEffectiveRetention(Path nodePath, boolean checkParent) throws RepositoryException {
+        if (!initialized) {
+            throw new IllegalStateException("Not initialized.");
+        }
+        if (retentionCnt <= 0) {
+            return false;
+        }
+        RetentionPolicy rp = null;
+        PathMap.Element element = retentionMap.map(nodePath, true);
+        if (element != null) {
+            rp = (RetentionPolicy) element.get();
+        }
+        if (rp == null && checkParent ) {
+            element = retentionMap.map(nodePath.getAncestor(1), true);
+            if (element != null) {
+                rp = (RetentionPolicy) element.get();
+            }
+        }
+        return rp != null;
+    }
+
+    //-------------------------------------------< SynchronousEventListener >---
+    /**
+     * @param events Events reporting hold/retention policy changes.
+     */
+    public void onEvent(EventIterator events) {
+        while (events.hasNext()) {
+            Event ev = events.nextEvent();
+            try {
+                Path evPath = session.getQPath(ev.getPath());
+                Path nodePath = evPath.getAncestor(1);
+                Name propName = evPath.getNameElement().getName();
+
+                if (RetentionManagerImpl.REP_HOLD.equals(propName)) {
+                    // hold changes
+                    switch (ev.getType()) {
+                        case Event.PROPERTY_ADDED:
+                        case Event.PROPERTY_CHANGED:
+                            // build the Hold objects from the rep:hold property
+                            // and put them into the hold map.
+                            PropertyImpl p = (PropertyImpl) session.getProperty(ev.getPath());
+                            addHolds(nodePath, p);
+                            break;
+                        case Event.PROPERTY_REMOVED:
+                            // all holds present on this node were remove
+                            // -> remove the corresponding entry in the holdMap.
+                            removeHolds(nodePath);
+                            break;
+                    }
+                } else if (RetentionManagerImpl.REP_RETENTION_POLICY.equals(propName)) {
+                    // retention policy changes
+                    switch (ev.getType()) {
+                        case Event.PROPERTY_ADDED:
+                        case Event.PROPERTY_CHANGED:
+                            // build the RetentionPolicy objects from the rep:retentionPolicy property
+                            // and put it into the retentionMap.
+                            PropertyImpl p = (PropertyImpl) session.getProperty(ev.getPath());
+                            addRetentionPolicy(nodePath, p);
+                            break;
+                        case Event.PROPERTY_REMOVED:
+                            // retention policy present on this node was remove
+                            // -> remove the corresponding entry in the retentionMap.
+                            removeRetentionPolicy(nodePath);
+                            break;
+                    }
+                }
+                // else: not interested in any other property -> ignore.
+
+            } catch (RepositoryException e) {
+                log.warn("Internal error while processing event.",e.getMessage());
+                // ignore.
+            }
+        }
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionRegistryImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionRegistryImpl.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLEditor.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLEditor.java?rev=738422&r1=738421&r2=738422&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLEditor.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLEditor.java Wed Jan 28 09:50:50 2009
@@ -22,13 +22,14 @@
 import org.apache.jackrabbit.api.jsr283.security.AccessControlPolicy;
 import org.apache.jackrabbit.api.jsr283.security.AccessControlList;
 import org.apache.jackrabbit.core.NodeImpl;
-import org.apache.jackrabbit.core.SecurityItemModifier;
+import org.apache.jackrabbit.core.ProtectedItemModifier;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
 import org.apache.jackrabbit.core.security.authorization.AccessControlEditor;
 import org.apache.jackrabbit.core.security.authorization.AccessControlUtils;
 import org.apache.jackrabbit.core.security.authorization.JackrabbitAccessControlEntry;
 import org.apache.jackrabbit.core.security.authorization.PrivilegeRegistry;
+import org.apache.jackrabbit.core.security.authorization.Permission;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.commons.conversion.NameException;
 import org.apache.jackrabbit.spi.commons.conversion.NameParser;
@@ -49,7 +50,7 @@
 /**
  * <code>ACLEditor</code>...
  */
-public class ACLEditor extends SecurityItemModifier implements AccessControlEditor, AccessControlConstants {
+public class ACLEditor extends ProtectedItemModifier implements AccessControlEditor, AccessControlConstants {
 
     /**
      * the default logger
@@ -67,7 +68,7 @@
     private final AccessControlUtils utils;
 
     ACLEditor(Session editingSession, AccessControlUtils utils) {
-        super(true);
+        super(Permission.MODIFY_AC);
         if (editingSession instanceof SessionImpl) {
             session = ((SessionImpl) editingSession);
             // TODO: review and find better solution
@@ -147,7 +148,7 @@
            access and removed the explicitely
          */
         if (aclNode != null) {
-            removeSecurityItem(aclNode);
+            removeItem(aclNode);
         }
         // now (re) create it
         aclNode = createAclNode(nodePath);
@@ -161,16 +162,16 @@
             ValueFactory vf = session.getValueFactory();
 
             // create the ACE node
-            NodeImpl aceNode = addSecurityNode(aclNode, nodeName, ntName);
+            NodeImpl aceNode = addNode(aclNode, nodeName, ntName);
 
             // write the rep:principalName property
             String principalName = ace.getPrincipal().getName();
-            setSecurityProperty(aceNode, P_PRINCIPAL_NAME, vf.createValue(principalName));
+            setProperty(aceNode, P_PRINCIPAL_NAME, vf.createValue(principalName));
 
             // ... and the rep:privileges property
             Privilege[] pvlgs = ace.getPrivileges();
             Value[] names = getPrivilegeNames(pvlgs, vf);
-            setSecurityProperty(aceNode, P_PRIVILEGES, names);
+            setProperty(aceNode, P_PRIVILEGES, names);
         }
     }
 
@@ -183,7 +184,7 @@
 
         NodeImpl aclNode = getAclNode(nodePath);
         if (aclNode != null) {
-            removeSecurityItem(aclNode);
+            removeItem(aclNode);
         } else {
             throw new AccessControlException("No policy to remove at " + nodePath);
         }
@@ -265,7 +266,7 @@
         if (!protectedNode.isNodeType(NT_REP_ACCESS_CONTROLLABLE)) {
             protectedNode.addMixin(NT_REP_ACCESS_CONTROLLABLE);
         }
-        return addSecurityNode(protectedNode, N_POLICY, NT_REP_ACL);
+        return addNode(protectedNode, N_POLICY, NT_REP_ACL);
     }
 
     /**

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLEditor.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLEditor.java?rev=738422&r1=738421&r2=738422&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLEditor.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLEditor.java Wed Jan 28 09:50:50 2009
@@ -23,11 +23,12 @@
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
 import org.apache.jackrabbit.api.security.principal.NoSuchPrincipalException;
 import org.apache.jackrabbit.core.NodeImpl;
-import org.apache.jackrabbit.core.SecurityItemModifier;
+import org.apache.jackrabbit.core.ProtectedItemModifier;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
 import org.apache.jackrabbit.core.security.authorization.AccessControlEditor;
 import org.apache.jackrabbit.core.security.authorization.JackrabbitAccessControlEntry;
+import org.apache.jackrabbit.core.security.authorization.Permission;
 import org.apache.jackrabbit.core.security.principal.ItemBasedPrincipal;
 import org.apache.jackrabbit.core.security.principal.PrincipalImpl;
 import org.apache.jackrabbit.spi.Name;
@@ -49,7 +50,7 @@
 /**
  * <code>CombinedEditor</code>...
  */
-public class ACLEditor extends SecurityItemModifier implements AccessControlEditor, AccessControlConstants {
+public class ACLEditor extends ProtectedItemModifier implements AccessControlEditor, AccessControlConstants {
 
     private static Logger log = LoggerFactory.getLogger(ACLEditor.class);
     /**
@@ -64,7 +65,7 @@
     private final String acRootPath;
 
     ACLEditor(SessionImpl session, Path acRootPath) throws RepositoryException {
-        super(true);
+        super(Permission.MODIFY_AC);
         this.session = session;
         this.acRootPath = session.getJCRPath(acRootPath);
     }
@@ -162,10 +163,10 @@
         NodeImpl aclNode;
         if (acNode.hasNode(N_POLICY)) {
             aclNode = acNode.getNode(N_POLICY);
-            removeSecurityItem(aclNode);
+            removeItem(aclNode);
         }
         /* now (re) create it */
-        aclNode = addSecurityNode(acNode, N_POLICY, NT_REP_ACL);
+        aclNode = addNode(acNode, N_POLICY, NT_REP_ACL);
 
         /* add all entries defined on the template */
         AccessControlEntry[] aces = acl.getAccessControlEntries();
@@ -175,25 +176,25 @@
             // create the ACE node
             Name nodeName = getUniqueNodeName(aclNode, "entry");
             Name ntName = (ace.isAllow()) ? NT_REP_GRANT_ACE : NT_REP_DENY_ACE;
-            NodeImpl aceNode = addSecurityNode(aclNode, nodeName, ntName);
+            NodeImpl aceNode = addNode(aclNode, nodeName, ntName);
 
             ValueFactory vf = session.getValueFactory();
             // write the rep:principalName property
-            setSecurityProperty(aceNode, P_PRINCIPAL_NAME, vf.createValue(ace.getPrincipal().getName()));
+            setProperty(aceNode, P_PRINCIPAL_NAME, vf.createValue(ace.getPrincipal().getName()));
             // ... and the rep:privileges property
             Privilege[] privs = ace.getPrivileges();
             Value[] vs = new Value[privs.length];
             for (int j = 0; j < privs.length; j++) {
                 vs[j] = vf.createValue(privs[j].getName(), PropertyType.NAME);
             }
-            setSecurityProperty(aceNode, P_PRIVILEGES, vs);
+            setProperty(aceNode, P_PRIVILEGES, vs);
 
             // store the restrictions:
             String[] restrNames = ace.getRestrictionNames();
             for (int rnIndex = 0; rnIndex < restrNames.length; rnIndex++) {
                 Name pName = session.getQName(restrNames[rnIndex]);
                 Value value = ace.getRestriction(restrNames[rnIndex]);
-                setSecurityProperty(aceNode, pName, value);
+                setProperty(aceNode, pName, value);
             }
         }
     }
@@ -210,7 +211,7 @@
             // build the template in order to have a return value
             AccessControlPolicy tmpl = createTemplate(acNode);
             if (tmpl.equals(policy)) {
-                removeSecurityItem(acNode.getNode(N_POLICY));
+                removeItem(acNode.getNode(N_POLICY));
                 return;
             }
         }
@@ -248,7 +249,7 @@
                     throw new RepositoryException("Internal error: Unexpected nodetype " + node.getPrimaryNodeType().getName() + " below /rep:accessControl");
                 }
             } else {
-                node = addSecurityNode(node, nName, NT_REP_ACCESS_CONTROL);
+                node = addNode(node, nName, NT_REP_ACCESS_CONTROL);
             }
         }
         return node;

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java?rev=738422&r1=738421&r2=738422&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java Wed Jan 28 09:50:50 2009
@@ -23,7 +23,7 @@
 import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.jackrabbit.core.ItemImpl;
 import org.apache.jackrabbit.core.NodeImpl;
-import org.apache.jackrabbit.core.SecurityItemModifier;
+import org.apache.jackrabbit.core.ProtectedItemModifier;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.security.principal.ItemBasedPrincipal;
 import org.apache.jackrabbit.core.security.principal.PrincipalImpl;
@@ -54,7 +54,7 @@
 /**
  * UserManagerImpl
  */
-public class UserManagerImpl extends SecurityItemModifier implements UserManager, UserConstants {
+public class UserManagerImpl extends ProtectedItemModifier implements UserManager, UserConstants {
 
     private static final Logger log = LoggerFactory.getLogger(UserManagerImpl.class);
 
@@ -71,7 +71,7 @@
     private final Map idPathMap = new LRUMap(1000);
 
     public UserManagerImpl(SessionImpl session, String adminId) throws RepositoryException {
-        super(false);
+        super();
         this.session = session;
         this.adminId = adminId;
 
@@ -216,11 +216,11 @@
             parent = createParentNode(parentPath);
 
             Name nodeName = session.getQName(Text.escapeIllegalJcrChars(userID));
-            NodeImpl userNode = addSecurityNode(parent, nodeName, NT_REP_USER);
+            NodeImpl userNode = addNode(parent, nodeName, NT_REP_USER);
 
-            setSecurityProperty(userNode, P_USERID, getValue(userID));
-            setSecurityProperty(userNode, P_PASSWORD, getValue(UserImpl.buildPasswordValue(password)));
-            setSecurityProperty(userNode, P_PRINCIPAL_NAME, getValue(principal.getName()));
+            setProperty(userNode, P_USERID, getValue(userID), true);
+            setProperty(userNode, P_PASSWORD, getValue(UserImpl.buildPasswordValue(password)), true);
+            setProperty(userNode, P_PRINCIPAL_NAME, getValue(principal.getName()), true);
             parent.save();
 
             log.debug("User created: " + userID + "; " + userNode.getPath());
@@ -271,8 +271,8 @@
             parent = createParentNode(parentPath);
             Name groupID = getGroupId(principal.getName());
 
-            NodeImpl groupNode = addSecurityNode(parent, groupID, NT_REP_GROUP);
-            setSecurityProperty(groupNode, P_PRINCIPAL_NAME, getValue(principal.getName()));
+            NodeImpl groupNode = addNode(parent, groupID, NT_REP_GROUP);
+            setProperty(groupNode, P_PRINCIPAL_NAME, getValue(principal.getName()));
             parent.save();
 
             log.debug("Group created: " + groupID + "; " + groupNode.getPath());
@@ -303,17 +303,17 @@
     }
 
     void setProtectedProperty(NodeImpl node, Name propName, Value value) throws RepositoryException, LockException, ConstraintViolationException, ItemExistsException, VersionException {
-        setSecurityProperty(node, propName, value);
+        setProperty(node, propName, value);
         node.save();
     }
 
     void setProtectedProperty(NodeImpl node, Name propName, Value[] values) throws RepositoryException, LockException, ConstraintViolationException, ItemExistsException, VersionException {
-        setSecurityProperty(node, propName, values);
+        setProperty(node, propName, values);
         node.save();
     }
 
     void removeProtectedItem(ItemImpl item, Node parent) throws RepositoryException, AccessDeniedException, VersionException {
-        removeSecurityItem(item);
+        removeItem(item);
         parent.save();
     }
 
@@ -491,7 +491,7 @@
                 } else {
                     ntName = NT_REP_AUTHORIZABLE_FOLDER;
                 }
-                NodeImpl added = addSecurityNode(parent, nName, ntName);
+                NodeImpl added = addNode(parent, nName, ntName);
                 parent.save();
                 parent = added;
             } else {

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java?rev=738422&r1=738421&r2=738422&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java Wed Jan 28 09:50:50 2009
@@ -146,7 +146,7 @@
             // create new with new uuid:
             // check if new node can be added (check access rights &
             // node type constraints only, assume locking & versioning status
-            // has already been checked on ancestor)
+            // and retention/hold has already been checked on ancestor)
             itemOps.checkAddNode(parent, nodeInfo.getName(),
                     nodeInfo.getNodeTypeName(),
                     BatchedItemOperations.CHECK_ACCESS
@@ -192,14 +192,16 @@
                     BatchedItemOperations.CHECK_ACCESS
                     | BatchedItemOperations.CHECK_LOCK
                     | BatchedItemOperations.CHECK_VERSIONING
-                    | BatchedItemOperations.CHECK_CONSTRAINTS);
+                    | BatchedItemOperations.CHECK_CONSTRAINTS
+                    | BatchedItemOperations.CHECK_HOLD
+                    | BatchedItemOperations.CHECK_RETENTION);
             // do remove conflicting (recursive)
             itemOps.removeNodeState(conflicting);
 
             // create new with given uuid:
             // check if new node can be added (check access rights &
             // node type constraints only, assume locking & versioning status
-            // has already been checked on ancestor)
+            // and retention/hold has already been checked on ancestor)
             itemOps.checkAddNode(parent, nodeInfo.getName(),
                     nodeInfo.getNodeTypeName(),
                     BatchedItemOperations.CHECK_ACCESS
@@ -231,7 +233,9 @@
                     BatchedItemOperations.CHECK_ACCESS
                     | BatchedItemOperations.CHECK_LOCK
                     | BatchedItemOperations.CHECK_VERSIONING
-                    | BatchedItemOperations.CHECK_CONSTRAINTS);
+                    | BatchedItemOperations.CHECK_CONSTRAINTS
+                    | BatchedItemOperations.CHECK_HOLD
+                    | BatchedItemOperations.CHECK_RETENTION);
 
             // 'replace' is actually a 'remove existing/add new' operation;
             // this unfortunately changes the order of the parent's
@@ -244,13 +248,16 @@
             itemOps.removeNodeState(conflicting);
             // create new with given uuid at same location as conflicting:
             // check if new node can be added at other location
-            // (access rights, node type constraints, locking & versioning status)
+            // (access rights, node type constraints, locking & versioning
+            // status and retention/hold)
             itemOps.checkAddNode(parent, nodeInfo.getName(),
                     nodeInfo.getNodeTypeName(),
                     BatchedItemOperations.CHECK_ACCESS
                     | BatchedItemOperations.CHECK_LOCK
                     | BatchedItemOperations.CHECK_VERSIONING
-                    | BatchedItemOperations.CHECK_CONSTRAINTS);
+                    | BatchedItemOperations.CHECK_CONSTRAINTS
+                    | BatchedItemOperations.CHECK_HOLD
+                    | BatchedItemOperations.CHECK_RETENTION);
             // do create new node
             node = itemOps.createNodeState(parent, nodeInfo.getName(),
                     nodeInfo.getNodeTypeName(), nodeInfo.getMixinNames(),
@@ -463,7 +470,7 @@
 
                     // check if new node can be added (check access rights &
                     // node type constraints only, assume locking & versioning status
-                    // has already been checked on ancestor)
+                    // and retention/hold has already been checked on ancestor)
                     itemOps.checkAddNode(parent, nodeName, ntName,
                             BatchedItemOperations.CHECK_ACCESS
                             | BatchedItemOperations.CHECK_CONSTRAINTS);
@@ -496,7 +503,7 @@
 
                         // check if new node can be added (check access rights &
                         // node type constraints only, assume locking & versioning status
-                        // has already been checked on ancestor)
+                        // and retention/hold has already been checked on ancestor)
                         itemOps.checkAddNode(parent, nodeName, ntName,
                                 BatchedItemOperations.CHECK_ACCESS
                                 | BatchedItemOperations.CHECK_CONSTRAINTS);

Modified: jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.cnd
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.cnd?rev=738422&r1=738421&r2=738422&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.cnd (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.cnd Wed Jan 28 09:50:50 2009
@@ -221,4 +221,13 @@
 
 [rep:AuthorizableFolder] > nt:base, mix:referenceable
   + * (rep:Authorizable) = rep:User protected version
-  + * (rep:AuthorizableFolder) = rep:AuthorizableFolder protected version
\ No newline at end of file
+  + * (rep:AuthorizableFolder) = rep:AuthorizableFolder protected version
+
+// -----------------------------------------------------------------------------
+// J A C K R A B B I T  R E T E N T I O N  M A N A G E M E N T
+// -----------------------------------------------------------------------------
+
+[rep:RetentionManageable]
+  mixin
+  - rep:hold (undefined) protected  multiple ignore
+  - rep:retentionPolicy (undefined) protected ignore
\ No newline at end of file

Modified: jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.xml
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.xml?rev=738422&r1=738421&r2=738422&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.xml (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.xml Wed Jan 28 09:50:50 2009
@@ -520,4 +520,11 @@
             </requiredPrimaryTypes>
         </childNodeDefinition>
     </nodeType>
+
+    <!-- retention management -->
+    <nodeType name="rep:RetentionManageable" isMixin="true" hasOrderableChildNodes="false" primaryItemName="">
+        <propertyDefinition name="rep:hold" requiredType="undefined" autoCreated="false" mandatory="false" onParentVersion="IGNORE" protected="true" multiple="true" />
+        <propertyDefinition name="rep:retentionPolicy" requiredType="undefined" autoCreated="false" mandatory="false" onParentVersion="IGNORE" protected="true" multiple="false" />        
+    </nodeType>
+    
 </nodeTypes>

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/SessionRemoveItemTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/SessionRemoveItemTest.java?rev=738422&r1=738421&r2=738422&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/SessionRemoveItemTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/SessionRemoveItemTest.java Wed Jan 28 09:50:50 2009
@@ -20,11 +20,13 @@
 import org.slf4j.LoggerFactory;
 import org.apache.jackrabbit.test.AbstractJCRTest;
 import org.apache.jackrabbit.test.NotExecutableException;
+import org.apache.jackrabbit.test.RepositoryStub;
 
 import javax.jcr.InvalidItemStateException;
 import javax.jcr.AccessDeniedException;
 import javax.jcr.RepositoryException;
 import javax.jcr.Property;
+import javax.jcr.Value;
 import javax.jcr.version.VersionException;
 import javax.jcr.lock.LockException;
 
@@ -129,7 +131,8 @@
     public void testRemoveLockedChildItem() throws RepositoryException, NotExecutableException {
         // add a child property and a child node to test deep lock effect.
         javax.jcr.Node childN = removeNode.addNode(nodeName2);
-        Property childP = removeNode.setProperty(propertyName2, "propvalue2");
+        Value v = getJcrValue(superuser, RepositoryStub.PROP_PROP_VALUE2, RepositoryStub.PROP_PROP_TYPE2, "propvalue2");        
+        Property childP = removeNode.setProperty(propertyName2, v);
         removeNode.save();
 
         if (!removeNode.isNodeType(mixLockable)) {
@@ -172,7 +175,8 @@
     public void testRemoveCheckedInItem() throws RepositoryException, NotExecutableException {
         // add a child property and a child node to test deep lock effect.
         javax.jcr.Node childN = removeNode.addNode(nodeName2);
-        Property childP = removeNode.setProperty(propertyName2, "propvalue2");
+        Value v = getJcrValue(superuser, RepositoryStub.PROP_PROP_VALUE2, RepositoryStub.PROP_PROP_TYPE2, "propvalue2");
+        Property childP = removeNode.setProperty(propertyName2, v);
         removeNode.save();
 
         if (!removeNode.isNodeType(mixVersionable)) {

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/retention/AbstractRetentionTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/retention/AbstractRetentionTest.java?rev=738422&r1=738421&r2=738422&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/retention/AbstractRetentionTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/retention/AbstractRetentionTest.java Wed Jan 28 09:50:50 2009
@@ -16,9 +16,11 @@
  */
 package org.apache.jackrabbit.api.jsr283.retention;
 
-import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.test.AbstractJCRTest;
 import org.apache.jackrabbit.test.NotExecutableException;
+import org.apache.jackrabbit.test.RepositoryStub;
+import org.apache.jackrabbit.core.retention.RetentionPolicyImpl;
+import org.apache.jackrabbit.core.SessionImpl;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
@@ -30,22 +32,38 @@
 public abstract class AbstractRetentionTest extends AbstractJCRTest {
 
     protected RetentionManager retentionMgr;
+    protected String testNodePath;
 
     protected void setUp() throws Exception {
         super.setUp();
 
+        // TODO: uncomment again.
+        // checkSupportedOption(Repository.OPTION_RETENTION_SUPPORTED);
+
         retentionMgr = getRetentionManager(superuser);
+        testNodePath = testRootNode.getPath();
     }
 
-    protected static RetentionManager getRetentionManager(Session s) throws RepositoryException, NotExecutableException {
-        // TODO: fix (Replace by Session) test as soon as jackrabbit implements 283
-        if (!(s instanceof SessionImpl)) {
+    protected String getHoldName() throws RepositoryException, NotExecutableException {
+        String holdName = getProperty(RepositoryStub.PROP_HOLD_NAME);
+        if (holdName == null) {
             throw new NotExecutableException();
         }
-        // TODO: uncomment again.
-        // checkSupportedOption(Repository.OPTION_RETENTION_SUPPORTED);
+        return holdName;
+    }
+
+    protected RetentionPolicy getApplicableRetentionPolicy() throws NotExecutableException, RepositoryException {
+        return getApplicableRetentionPolicy("retentionPolicyName");
+    }
+
+    protected RetentionPolicy getApplicableRetentionPolicy(String jcrName) throws NotExecutableException, RepositoryException {
+        // TODO: move to repositoryStub/helper and adjust accordingly
+        return new RetentionPolicyImpl(jcrName, (SessionImpl)superuser);
+    }
+
+    protected static RetentionManager getRetentionManager(Session s) throws RepositoryException, NotExecutableException {
         try {
-            return ((SessionImpl) s).getRetentionManager();
+            return getJsr283Session(s).getRetentionManager();
         } catch (UnsupportedRepositoryOperationException e) {
             throw new NotExecutableException();
         }
@@ -56,4 +74,13 @@
             throw new NotExecutableException();
         }
     }
+
+    protected static org.apache.jackrabbit.api.jsr283.Session getJsr283Session(Session s) throws NotExecutableException {
+        // TODO: get rid of method once jsr 283 is released
+        if (s instanceof org.apache.jackrabbit.api.jsr283.Session) {
+            return (org.apache.jackrabbit.api.jsr283.Session) s;
+        } else {
+            throw new NotExecutableException();
+        }
+    }
 }

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/retention/HoldEffectTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/retention/HoldEffectTest.java?rev=738422&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/retention/HoldEffectTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/retention/HoldEffectTest.java Wed Jan 28 09:50:50 2009
@@ -0,0 +1,225 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.api.jsr283.retention;
+
+import org.apache.jackrabbit.test.NotExecutableException;
+import org.apache.jackrabbit.test.RepositoryStub;
+
+import javax.jcr.Session;
+import javax.jcr.RepositoryException;
+import javax.jcr.Property;
+import javax.jcr.Node;
+import javax.jcr.Value;
+import javax.jcr.nodetype.NodeType;
+
+/**
+ * <code>HoldEffectTest</code>...
+ */
+public class HoldEffectTest extends AbstractRetentionTest {
+
+    private Node childN;
+    private Property childP;
+    private Session otherS;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        childN = testRootNode.addNode(nodeName2);
+        Value v = getJcrValue(superuser, RepositoryStub.PROP_PROP_VALUE1, RepositoryStub.PROP_PROP_TYPE1, "test");                
+        childP = testRootNode.setProperty(propertyName1, v);
+        superuser.save();
+
+        otherS = helper.getSuperuserSession();
+    }
+
+    protected void tearDown() throws Exception {
+        if (otherS != null) {
+            otherS.logout();
+        }
+        Hold[] holds = retentionMgr.getHolds(testNodePath);
+        for (int i = 0; i < holds.length; i++) {
+            retentionMgr.removeHold(testNodePath, holds[i]);
+        }
+        superuser.save();
+        
+        super.tearDown();
+    }
+
+    // TODO: test importXML (session/wsp) / move (session/wsp) / copy ...
+    // TODO: test effect on child items
+    
+    public void testTransientShallowHold() throws RepositoryException, NotExecutableException {
+        retentionMgr.addHold(testNodePath, getHoldName(), false);
+
+        assertNoEffect(testRootNode, nodeName3, propertyName2);
+        assertNoEffect(childN, nodeName3, propertyName2);
+        assertNoEffect(childP);
+    }
+
+    public void testTransientShallowHoldForOtherSession() throws RepositoryException, NotExecutableException {
+        retentionMgr.addHold(testNodePath, getHoldName(), false);
+
+        assertNoEffect((Node) otherS.getItem(testNodePath), nodeName3, propertyName2);
+        assertNoEffect((Node) otherS.getItem(childN.getPath()), nodeName3, propertyName2);
+        assertNoEffect((Property) otherS.getItem(childP.getPath()));
+    }
+
+    public void testTransientDeepHold() throws RepositoryException, NotExecutableException {
+        retentionMgr.addHold(testNodePath, getHoldName(), true);
+
+        assertNoEffect(testRootNode, nodeName3, propertyName2);
+        assertNoEffect(childN, nodeName3, propertyName2);
+        assertNoEffect(childP);
+    }
+
+    public void testTransientDeepHoldForOtherSession() throws RepositoryException, NotExecutableException {
+        retentionMgr.addHold(testNodePath, getHoldName(), true);
+
+        assertNoEffect((Node) otherS.getItem(testNodePath), nodeName3, propertyName2);
+        assertNoEffect((Node) otherS.getItem(childN.getPath()), nodeName3, propertyName2);
+        assertNoEffect((Property) otherS.getItem(childP.getPath()));
+    }
+
+    public void testShallowHold() throws RepositoryException, NotExecutableException {
+        retentionMgr.addHold(testNodePath, getHoldName(), false);
+        superuser.save();
+
+        // check for superuser
+        assertNoEffect(childN, nodeName3, propertyName2);
+        assertEffect(testRootNode, childN.getName(), childP.getName(), nodeName3, propertyName2);
+    }
+
+    public void testShallowHoldForOtherSession() throws RepositoryException, NotExecutableException {
+        retentionMgr.addHold(testNodePath, getHoldName(), false);
+        superuser.save();
+        
+        // check for other session
+        assertNoEffect((Node) otherS.getItem(childN.getPath()), nodeName3, propertyName2);
+        assertEffect((Node) otherS.getItem(testNodePath), childN.getName(), childP.getName(), nodeName3, propertyName2);
+    }
+
+    public void testDeepHold() throws RepositoryException, NotExecutableException {
+        Node n = childN.addNode(nodeName2);
+        Value v = getJcrValue(superuser, RepositoryStub.PROP_PROP_VALUE1, RepositoryStub.PROP_PROP_TYPE1, "test");
+        Property p = childN.setProperty(propertyName1, v);
+        retentionMgr.addHold(testNodePath, getHoldName(), true);
+        superuser.save();
+
+        // check for superuser
+        assertEffect(testRootNode, childN.getName(), childP.getName(), nodeName3, propertyName2);
+        assertEffect(childN, n.getName(), p.getName(), nodeName3, propertyName2);
+    }
+
+    public void testDeepHoldForOtherSession() throws RepositoryException, NotExecutableException {
+        Node n = childN.addNode(nodeName2);
+        Value v = getJcrValue(superuser, RepositoryStub.PROP_PROP_VALUE1, RepositoryStub.PROP_PROP_TYPE1, "test");
+        Property p = childN.setProperty(propertyName1, v);
+        retentionMgr.addHold(testNodePath, getHoldName(), true);
+        superuser.save();
+
+        // check for other session
+        assertEffect((Node) otherS.getItem(testNodePath), childN.getName(), childP.getName(), nodeName3, propertyName2);
+        assertEffect((Node) otherS.getItem(childN.getPath()), n.getName(), p.getName(), nodeName3, propertyName2);
+    }
+
+    private void assertEffect(Node targetNode, String childName,
+                                        String propName, String childName2,
+                                        String propName2) throws RepositoryException {
+        Session s = targetNode.getSession();
+        try {
+            Node child = targetNode.getNode(childName);
+            child.remove();
+            s.save();
+            fail("Hold present must prevent a child node from being removed.");
+        } catch (RepositoryException e) {
+            // success
+            s.refresh(false);
+        }
+        try {
+            Property p = targetNode.getProperty(propName);
+            p.remove();
+            s.save();
+            fail("Hold present must prevent a child property from being removed.");
+        } catch (RepositoryException e) {
+            // success
+            s.refresh(false);
+        }
+        try {
+            Property p = targetNode.getProperty(propName);
+            p.setValue("test2");
+            s.save();
+            fail("Hold present must prevent the child property from being modified.");
+        } catch (RepositoryException e) {
+            // success
+            s.refresh(false);
+        }
+        try {
+            targetNode.addNode(childName2);
+            s.save();
+            fail("Hold present must prevent the target node from having new nodes added.");
+        } catch (RepositoryException e) {
+            // success
+            s.refresh(false);
+        }
+        try {
+            Value v = getJcrValue(s, RepositoryStub.PROP_PROP_VALUE2, RepositoryStub.PROP_PROP_TYPE2, "test");
+            targetNode.setProperty(propName2, v);
+            s.save();
+            fail("Hold present must prevent the target node from having new properties set.");
+        } catch (RepositoryException e) {
+            // success
+            s.refresh(false);
+        }
+
+        NodeType[] mixins = targetNode.getMixinNodeTypes();
+        if (mixins.length > 0) {
+            try {
+                targetNode.removeMixin(mixins[0].getName());
+                s.save();
+                fail("Hold present must prevent the target node from having it's mixin types changed.");
+            }  catch (RepositoryException e) {
+                // success
+                s.refresh(false);
+            }
+        }
+        try {
+            targetNode.remove();
+            s.save();
+            fail("Hold present must prevent the target node from being removed.");
+        } catch (RepositoryException e) {
+            // success
+            s.refresh(false);
+        }
+    }
+
+    private void assertNoEffect(Node target, String childName, String propName) throws RepositoryException {
+        Session s = target.getSession();
+
+        Node n = target.addNode(childName);
+        Value v = getJcrValue(s, RepositoryStub.PROP_PROP_VALUE2, RepositoryStub.PROP_PROP_TYPE2, "test");
+        Property p = target.setProperty(propName, v);
+
+        n.remove();
+        p.remove();
+    }
+
+    private void assertNoEffect(Property target) throws RepositoryException {
+        Session s = target.getSession();
+        target.setValue("test3");
+        target.remove();
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/retention/HoldEffectTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/retention/HoldEffectTest.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/retention/HoldTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/retention/HoldTest.java?rev=738422&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/retention/HoldTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/retention/HoldTest.java Wed Jan 28 09:50:50 2009
@@ -0,0 +1,425 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.api.jsr283.retention;
+
+import org.apache.jackrabbit.test.NotExecutableException;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.PropertyIterator;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.lock.LockException;
+import javax.jcr.version.VersionException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * <code>RetentionManagerTest</code>...
+ */
+public class HoldTest extends AbstractRetentionTest {
+
+    private static boolean containsHold(Hold[] holds, Hold toTest) throws RepositoryException {
+        for (int i = 0; i < holds.length; i++) {
+            if (holds[i].getName().equals(toTest.getName()) && holds[i].isDeep() == toTest.isDeep()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void testAddHold() throws RepositoryException, NotExecutableException {
+        Hold hold = retentionMgr.addHold(testNodePath, getHoldName(), false);
+        Hold[] holds = retentionMgr.getHolds(testNodePath);
+        assertTrue("getHolds must return the hold added before.", holds.length >= 1);
+        assertTrue("getHolds doesn't return the hold added before", containsHold(holds, hold));
+    }
+
+    public void testAddHold2() throws RepositoryException, NotExecutableException {
+        Hold[] holdsBefore = retentionMgr.getHolds(testNodePath);
+        Hold hold = retentionMgr.addHold(testNodePath, getHoldName(), false);
+        assertFalse("The hold added must not have been present before.", containsHold(holdsBefore, hold));
+    }
+
+    public void testAddHoldIsTransient() throws RepositoryException, NotExecutableException {
+        Hold hold = retentionMgr.addHold(testNodePath, getHoldName(), false);
+        Hold[] holds = retentionMgr.getHolds(testNodePath);
+
+        // revert the changes made
+        superuser.refresh(false);
+        Hold[] holds2 = retentionMgr.getHolds(testNodePath);
+
+        assertEquals("Reverting transient changes must revert the hold added.",
+                holds.length -1, holds2.length);
+        assertFalse("Reverting transient changes must revert the hold added.",
+                containsHold(holds2, hold));
+    }
+    
+    public void testRemoveHold() throws RepositoryException, NotExecutableException {
+        Hold hold = retentionMgr.addHold(testNodePath, getHoldName(), false);
+
+        Hold[] holds = retentionMgr.getHolds(testNodePath);
+
+        retentionMgr.removeHold(testNodePath, hold);
+        Hold[] holds2 = retentionMgr.getHolds(testNodePath);
+
+        assertEquals("RetentionManager.removeHold should removed the hold added before.",
+                holds.length -1, holds2.length);
+        assertFalse("RetentionManager.removeHold should removed the hold added before.",
+                containsHold(holds2, hold));
+    }
+
+    public void testRemoveHoldIsTransient() throws RepositoryException, NotExecutableException {
+        Hold hold = retentionMgr.addHold(testNodePath, getHoldName(), false);
+        superuser.save();
+        try {
+            Hold[] holds = retentionMgr.getHolds(testNodePath);
+
+            retentionMgr.removeHold(testNodePath, hold);
+            superuser.refresh(false);
+
+            Hold[] holds2 = retentionMgr.getHolds(testNodePath);
+            assertEquals("Reverting transient hold removal must restore the original state.",
+                    Arrays.asList(holds), Arrays.asList(holds2));
+        } finally {
+            // clear the hold that was permanently added before.
+            retentionMgr.removeHold(testNodePath, hold);
+            superuser.save();
+        }
+    }
+
+    public void testRemoveHoldFromChild() throws RepositoryException, NotExecutableException {
+        String childPath = testRootNode.addNode(nodeName2, testNodeType).getPath();
+        Hold hold = retentionMgr.addHold(testNodePath, getHoldName(), false);
+
+        try {
+            retentionMgr.removeHold(childPath, hold);
+            fail("Removing hold from another node must fail");
+        } catch (RepositoryException e) {
+            // success
+            assertTrue(containsHold(retentionMgr.getHolds(testNodePath), hold));
+        }
+
+        // check again with persisted hold
+        superuser.save();
+        try {
+            retentionMgr.removeHold(childPath, hold);
+            fail("Removing hold from another node must fail");
+        } catch (RepositoryException e) {
+            // success
+            assertTrue(containsHold(retentionMgr.getHolds(testNodePath), hold));
+        } finally {
+            // clear the hold that was permanently added before.
+            retentionMgr.removeHold(testNodePath, hold);
+            superuser.save();
+        }
+    }
+
+    public void testInvalidPath() throws RepositoryException, NotExecutableException {
+        String invalidPath = testPath; // not an absolute path.
+        try {
+            retentionMgr.getHolds(invalidPath);
+            fail("Accessing holds an invalid path must throw RepositoryException.");
+        } catch (RepositoryException e) {
+            // success
+        }
+        try {
+            retentionMgr.addHold(invalidPath, getHoldName(), true);
+            fail("Adding a hold at an invalid path must throw RepositoryException.");
+        } catch (RepositoryException e) {
+            // success
+        }
+        try {
+            Hold h = retentionMgr.addHold(testNodePath, getHoldName(), true);
+            retentionMgr.removeHold(invalidPath, h);
+            fail("Removing a hold at an invalid path must throw RepositoryException.");
+        } catch (RepositoryException e) {
+            // success
+        }
+    }
+
+    public void testNonExistingNodePath() throws RepositoryException, NotExecutableException {
+        String invalidPath = testNodePath + "/nonexisting";
+        int cnt = 0;
+        while (getJsr283Session(superuser).nodeExists(invalidPath)) {
+            invalidPath += cnt++;
+        }
+
+        try {
+            retentionMgr.getHolds(invalidPath);
+            fail("Accessing holds from non-existing node must throw PathNotFoundException.");
+        } catch (PathNotFoundException e) {
+            // success
+        }
+        try {
+            retentionMgr.addHold(invalidPath, getHoldName(), true);
+            fail("Adding a hold for a non-existing node must throw PathNotFoundException.");
+        } catch (PathNotFoundException e) {
+            // success
+        }
+        try {
+            Hold h = retentionMgr.addHold(testNodePath, getHoldName(), true);
+            retentionMgr.removeHold(invalidPath, h);
+            fail("Removing a hold at a non-existing node must throw PathNotFoundException.");
+        } catch (PathNotFoundException e) {
+            // success
+        }
+    }
+
+    public void testPropertyPath() throws RepositoryException, NotExecutableException {
+        String propPath = null;
+        for (PropertyIterator it = testRootNode.getProperties(); it.hasNext();) {
+            String path = it.nextProperty().getPath();
+            if (!getJsr283Session(superuser).nodeExists(path)) {
+                propPath = path;
+                break;
+            }
+        }
+        if (propPath == null) {
+            throw new NotExecutableException();
+        }
+        try {
+            retentionMgr.getHolds(propPath);
+            fail("Accessing holds from non-existing node must throw PathNotFoundException.");
+        } catch (PathNotFoundException e) {
+            // success
+        }
+        try {
+            retentionMgr.addHold(propPath, getHoldName(), true);
+            fail("Adding a hold for a non-existing node must throw PathNotFoundException.");
+        } catch (PathNotFoundException e) {
+            // success
+        }
+        try {
+            Hold h = retentionMgr.addHold(testNodePath, getHoldName(), true);
+            retentionMgr.removeHold(propPath, h);
+            fail("Removing a hold at a non-existing node must throw PathNotFoundException.");
+        } catch (PathNotFoundException e) {
+            // success
+        }
+    }
+
+    public void testInvalidName() {
+        try {
+            String invalidName = "*.[y]";
+            retentionMgr.addHold(testNodePath, invalidName, false);
+            fail("Adding a hold with an invalid JCR name must fail.");
+        } catch (RepositoryException e) {
+            // success
+        }
+    }
+
+    public void testReadOnlySession() throws NotExecutableException, RepositoryException {
+        javax.jcr.Session s = helper.getReadOnlySession();
+        try {
+            RetentionManager rmgr = getRetentionManager(s);
+            try {
+                rmgr.getHolds(testNodePath);
+                fail("Read-only session doesn't have sufficient privileges to retrieve holds.");
+            } catch (AccessDeniedException e) {
+                // success
+            }
+            try {
+                rmgr.addHold(testNodePath, getHoldName(), false);
+                fail("Read-only session doesn't have sufficient privileges to retrieve holds.");
+            } catch (AccessDeniedException e) {
+                // success
+            }
+        } finally {
+            s.logout();
+        }
+    }
+
+    public void testAddHoldOnLockedNode() throws NotExecutableException, RepositoryException {
+        Node child = getLockedChildNode();
+        // remember current holds for clean up.
+        List holdsBefore = Arrays.asList(retentionMgr.getHolds(child.getPath()));
+
+        // get another session.
+        javax.jcr.Session otherS = helper.getSuperuserSession();
+        try {
+            RetentionManager rmgr = getRetentionManager(otherS);            
+            rmgr.addHold(child.getPath(), getHoldName(), false);
+            otherS.save();
+
+            fail("Adding hold on a locked node must throw LockException.");
+        } catch (LockException e) {
+            // success
+        } finally {
+            otherS.logout();
+
+            // clear holds (in case of test failure)
+            List holds = new ArrayList(Arrays.asList(retentionMgr.getHolds(child.getPath())));
+            if (holds.removeAll(holdsBefore)) {
+                for (Iterator it = holds.iterator(); it.hasNext();) {
+                    retentionMgr.removeHold(child.getPath(), (Hold) it.next());
+                }
+            }
+            superuser.save();
+        }
+    }
+
+    public void testRemoveHoldOnLockedNode() throws NotExecutableException, RepositoryException {
+        Node child = getLockedChildNode();
+        Hold h = retentionMgr.addHold(child.getPath(), getHoldName(), false);
+        testRootNode.save();
+
+        javax.jcr.Session otherS = helper.getSuperuserSession();
+        try {
+            RetentionManager rmgr = getRetentionManager(otherS);
+            Hold[] holds = rmgr.getHolds(child.getPath());
+
+            if (holds.length > 0) {
+                rmgr.removeHold(child.getPath(), holds[0]);
+                otherS.save();
+                fail("Removing a hold on a locked node must throw LockException.");
+            }
+        } catch (LockException e) {
+            // success
+        } finally {
+            otherS.logout();
+
+            // clear hold added before
+            try {
+                retentionMgr.removeHold(child.getPath(), h);
+                superuser.save();
+            } catch (RepositoryException e) {
+                // should not get here if test is correctly executed.
+            }
+        }
+    }
+
+    private Node getLockedChildNode() throws NotExecutableException, RepositoryException {
+        checkSupportedOption(superuser, Repository.OPTION_LOCKING_SUPPORTED);
+        Node child = testRootNode.addNode(nodeName2, testNodeType);
+        if (!child.isNodeType(mixLockable)) {
+            if (child.canAddMixin(mixLockable)) {
+                child.addMixin(mixLockable);
+            } else {
+                throw new NotExecutableException();
+            }
+        }
+        testRootNode.save();
+        child.lock(false, true); // session-scoped lock clean upon superuser-logout.
+        return child;
+    }
+
+    public void testAddHoldOnCheckedInNode() throws NotExecutableException, RepositoryException {
+        Node child = getVersionableChildNode();
+        child.checkout();
+        child.checkin();
+
+        // get another session.
+        javax.jcr.Session otherS = helper.getSuperuserSession();
+        try {
+            RetentionManager rmgr = getRetentionManager(otherS);
+            rmgr.addHold(child.getPath(), getHoldName(), false);
+            otherS.save();
+
+            fail("Adding hold on a checked-in node must throw VersionException.");
+        } catch (VersionException e) {
+            // success
+        } finally {
+            otherS.logout();
+
+            // clear holds (in case of test failure)
+            child.checkout();
+            Hold[] holds = retentionMgr.getHolds(child.getPath());
+            for (int i = 0; i < holds.length; i++) {
+                retentionMgr.removeHold(child.getPath(), holds[i]);
+            }
+            superuser.save();
+        }
+    }
+
+    public void testRemoveHoldOnCheckedInNode() throws NotExecutableException, RepositoryException {
+        Node vn = getVersionableChildNode();
+        vn.checkout();
+        Node n = vn.addNode(nodeName2);
+        Hold h = retentionMgr.addHold(n.getPath(), getHoldName(), false);
+        superuser.save();
+
+        // checkin on the parent node make the hold-containing node checked-in.
+        vn.checkin();
+
+        javax.jcr.Session otherS = helper.getSuperuserSession();
+        try {
+            RetentionManager rmgr = getRetentionManager(otherS);
+            Hold[] holds = rmgr.getHolds(n.getPath());
+
+            if (holds.length > 0) {
+                rmgr.removeHold(n.getPath(), holds[0]);
+                otherS.save();
+                fail("Removing a hold on a checked-in node must throw VersionException.");
+            }
+        } catch (VersionException e) {
+            // success
+        } finally {
+            otherS.logout();
+
+            // clear hold added before
+            vn.checkout();
+            try {
+                retentionMgr.removeHold(n.getPath(), h);
+                superuser.save();
+            } catch (RepositoryException e) {
+                // should not get here if test is correctly executed.
+            }
+        }
+    }
+
+    private Node getVersionableChildNode() throws NotExecutableException, RepositoryException {
+        checkSupportedOption(superuser, Repository.OPTION_VERSIONING_SUPPORTED);
+        Node child = testRootNode.addNode(nodeName2, testNodeType);
+        if (!child.isNodeType(mixVersionable)) {
+            if (child.canAddMixin(mixVersionable)) {
+                child.addMixin(mixVersionable);
+            } else {
+                throw new NotExecutableException();
+            }
+        }
+        testRootNode.save();
+        return child;
+    }
+
+    public void testHoldGetName() throws RepositoryException, NotExecutableException {
+        String holdName = getHoldName();
+        Hold h = retentionMgr.addHold(testNodePath, getHoldName(), false);
+        assertEquals("Hold.getName() must return the specified name.",holdName, h.getName());
+    }
+
+    public void testHoldGetName2() throws RepositoryException, NotExecutableException {
+        String holdName = getHoldName();
+        Hold h = retentionMgr.addHold(testNodePath, getHoldName(), true);
+        assertEquals("Hold.getName() must return the specified name.",holdName, h.getName());
+    }
+
+    public void testHoldIsDeep() throws RepositoryException, NotExecutableException {
+        String holdName = getHoldName();
+        Hold h = retentionMgr.addHold(testNodePath, getHoldName(), false);
+        assertEquals("Hold.isDeep() must reflect the specified flag.", false, h.isDeep());
+    }
+
+    public void testHoldIsDeep2() throws RepositoryException, NotExecutableException {
+        String holdName = getHoldName();
+        Hold h = retentionMgr.addHold(testNodePath, getHoldName(), true);
+        assertEquals("Hold.isDeep() must reflect the specified flag.", true, h.isDeep());
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/retention/HoldTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/retention/HoldTest.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url