Author: jukka
Date: Wed Mar 26 05:23:33 2014
New Revision: 1581696

URL: http://svn.apache.org/r1581696
Log:
OAK-1329: Relaxed JCR locking behavior

Setting the oak.relaxed-locking session attribute to true enables
the relaxed locking rules that allow a session to unlock a lock owned
by the current user without first explicitly acquiring the relevant
lock token.

This is a convenience feature without security implications as we already
expose the lock token through Lock.getLockToken() to all sessions of the
user who owns the lock. Thus in practice this feature just removes the
need to do lockManager.addLockToken(node.getLock().getLockToken())
before unlocking a node with node.unlock().

Modified:
    
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java
    
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/lock/LockImpl.java
    
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/lock/LockManagerImpl.java
    
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
    
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/NodeImpl.java

Modified: 
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java?rev=1581696&r1=1581695&r2=1581696&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java
 Wed Mar 26 05:23:33 2014
@@ -21,6 +21,7 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
+
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 import javax.jcr.InvalidItemStateException;
@@ -34,6 +35,7 @@ import javax.jcr.security.AccessControlE
 
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
+
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Root;
@@ -774,6 +776,10 @@ public class NodeDelegate extends ItemDe
         }
     }
 
+    public boolean isLockOwner(String user) {
+        return user != null && user.equals(getLockOwner());
+    }
+
     public void lock(boolean isDeep) throws RepositoryException {
         String path = getPath();
 

Modified: 
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/lock/LockImpl.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/lock/LockImpl.java?rev=1581696&r1=1581695&r2=1581696&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/lock/LockImpl.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/lock/LockImpl.java
 Wed Mar 26 05:23:33 2014
@@ -98,18 +98,16 @@ public final class LockImpl implements L
                     // another session of the lock owner will be able to
                     // acquire the lock token and thus release the lock.
                     return null;
-                }
-
-                String owner =
-                        context.getSessionDelegate().getAuthInfo().getUserID();
-                if (owner == null) {
-                    owner = "";
-                }
-                if (owner.equals(node.getLockOwner())) {
+                } else if (node.isLockOwner(
+                        
context.getSessionDelegate().getAuthInfo().getUserID())) {
+                    // The JCR spec allows the implementation to return the
+                    // lock token even when the current session isn't already
+                    // holding it. We use this feature to allow all sessions
+                    // of the user who owns the lock to access its token.
                     return token;
+                } else {
+                    return null;
                 }
-
-                return null;
             }
         });
     }

Modified: 
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/lock/LockManagerImpl.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/lock/LockManagerImpl.java?rev=1581696&r1=1581695&r2=1581696&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/lock/LockManagerImpl.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/lock/LockManagerImpl.java
 Wed Mar 26 05:23:33 2014
@@ -16,6 +16,9 @@
  */
 package org.apache.jackrabbit.oak.jcr.lock;
 
+import static java.lang.Boolean.TRUE;
+import static 
org.apache.jackrabbit.oak.jcr.repository.RepositoryImpl.RELAXED_LOCKING;
+
 import java.util.Set;
 
 import javax.annotation.Nonnull;
@@ -167,8 +170,7 @@ public class LockManagerImpl implements 
             protected Void perform(NodeDelegate node)
                     throws RepositoryException {
                 String path = node.getPath();
-                if (sessionContext.getSessionScopedLocks().contains(path)
-                        || sessionContext.getOpenScopedLocks().contains(path)) 
{
+                if (canUnlock(path, node)) {
                     node.unlock();
                     sessionContext.getSessionScopedLocks().remove(path);
                     sessionContext.getOpenScopedLocks().remove(path);
@@ -178,6 +180,17 @@ public class LockManagerImpl implements 
                     throw new LockException("Not an owner of the lock " + 
path);
                 }
             }
+            private boolean canUnlock(String path, NodeDelegate node) {
+                if (sessionContext.getSessionScopedLocks().contains(path)
+                        || sessionContext.getOpenScopedLocks().contains(path)) 
{
+                    return true;
+                } else if (sessionContext.getAttributes().get(RELAXED_LOCKING) 
== TRUE) {
+                    String user = 
sessionContext.getSessionDelegate().getAuthInfo().getUserID();
+                    return node.isLockOwner(user);
+                } else {
+                    return false;
+                }
+            }
         });
     }
 

Modified: 
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java?rev=1581696&r1=1581695&r2=1581696&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
 Wed Mar 26 05:23:33 2014
@@ -1,412 +1,444 @@
-/*
- * 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.oak.jcr.repository;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.jcr.Credentials;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-import javax.jcr.Value;
-import javax.security.auth.login.LoginException;
-
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableScheduledFuture;
-import com.google.common.util.concurrent.ListeningScheduledExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-import org.apache.jackrabbit.api.JackrabbitRepository;
-import 
org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
-import org.apache.jackrabbit.commons.SimpleValueFactory;
-import org.apache.jackrabbit.oak.api.ContentRepository;
-import org.apache.jackrabbit.oak.api.ContentSession;
-import org.apache.jackrabbit.oak.api.jmx.SessionMBean;
-import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate;
-import org.apache.jackrabbit.oak.jcr.session.RefreshStrategy;
-import org.apache.jackrabbit.oak.jcr.session.SessionContext;
-import org.apache.jackrabbit.oak.jcr.session.SessionStats;
-import org.apache.jackrabbit.oak.plugins.observation.CommitRateLimiter;
-import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
-import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
-import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
-import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils;
-import org.apache.jackrabbit.oak.stats.Clock;
-import org.apache.jackrabbit.oak.stats.StatisticManager;
-import org.apache.jackrabbit.oak.util.GenericDescriptors;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * TODO document
- */
-public class RepositoryImpl implements JackrabbitRepository {
-
-    /**
-     * logger instance
-     */
-    private static final Logger log = 
LoggerFactory.getLogger(RepositoryImpl.class);
-
-    /**
-     * Name of the session attribute value determining the session refresh
-     * interval in seconds.
-     *
-     * @see org.apache.jackrabbit.oak.jcr.session.RefreshStrategy
-     */
-    public static final String REFRESH_INTERVAL = "oak.refresh-interval";
-
-    private final GenericDescriptors descriptors;
-    private final ContentRepository contentRepository;
-    protected final Whiteboard whiteboard;
-    private final SecurityProvider securityProvider;
-    private final int observationQueueLength;
-    private final CommitRateLimiter commitRateLimiter;
-
-    private final Clock clock;
-
-    /**
-     * {@link ThreadLocal} counter that keeps track of the save operations
-     * performed per thread so far. This is is then used to determine if
-     * the current session needs to be refreshed to see the changes done by
-     * another session in the same thread.
-     * <p>
-     * <b>Note</b> - This thread local is never cleared. However, we only
-     * store a {@link Long} instance and do not derive from
-     * {@link ThreadLocal} so that (class loader) leaks typically associated
-     * with thread locals do not occur.
-     */
-    private final ThreadLocal<Long> threadSaveCount = new ThreadLocal<Long>();
-
-    private final ListeningScheduledExecutorService scheduledExecutor =
-            
MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor());
-    private final StatisticManager statisticManager;
-
-    public RepositoryImpl(@Nonnull ContentRepository contentRepository,
-                          @Nonnull Whiteboard whiteboard,
-                          @Nonnull SecurityProvider securityProvider,
-                          int observationQueueLength,
-                          CommitRateLimiter commitRateLimiter) {
-        this.contentRepository = checkNotNull(contentRepository);
-        this.whiteboard = checkNotNull(whiteboard);
-        this.securityProvider = checkNotNull(securityProvider);
-        this.observationQueueLength = observationQueueLength;
-        this.commitRateLimiter = commitRateLimiter;
-        this.descriptors = determineDescriptors();
-        this.statisticManager = new StatisticManager(whiteboard, 
scheduledExecutor);
-        this.clock = new Clock.Fast(scheduledExecutor);
-    }
-
-    //---------------------------------------------------------< Repository 
>---
-    /**
-     * @see javax.jcr.Repository#getDescriptorKeys()
-     */
-    @Override
-    public String[] getDescriptorKeys() {
-        return descriptors.getKeys();
-    }
-
-    /**
-     * @see Repository#isStandardDescriptor(String)
-     */
-    @Override
-    public boolean isStandardDescriptor(String key) {
-        return descriptors.isStandardDescriptor(key);
-    }
-
-    /**
-     * @see javax.jcr.Repository#getDescriptor(String)
-     */
-    @Override
-    public String getDescriptor(String key) {
-        try {
-            Value v = getDescriptorValue(key);
-            return v == null
-                    ? null
-                    : v.getString();
-        } catch (RepositoryException e) {
-            log.debug("Error converting value for descriptor with key {} to 
string", key);
-            return null;
-        }
-    }
-
-    /**
-     * @see javax.jcr.Repository#getDescriptorValue(String)
-     */
-    @Override
-    public Value getDescriptorValue(String key) {
-        return descriptors.getValue(key);
-    }
-
-    /**
-     * @see javax.jcr.Repository#getDescriptorValues(String)
-     */
-    @Override
-    public Value[] getDescriptorValues(String key) {
-        return descriptors.getValues(key);
-    }
-
-    /**
-     * @see javax.jcr.Repository#isSingleValueDescriptor(String)
-     */
-    @Override
-    public boolean isSingleValueDescriptor(String key) {
-        return descriptors.isSingleValueDescriptor(key);
-    }
-
-    /**
-     * @see javax.jcr.Repository#login(javax.jcr.Credentials, String)
-     */
-    @Override
-    public Session login(@Nullable Credentials credentials, @Nullable String 
workspaceName)
-            throws RepositoryException {
-        return login(credentials, workspaceName, null);
-    }
-
-    /**
-     * Calls {@link Repository#login(Credentials, String)} with
-     * {@code null} arguments.
-     *
-     * @return logged in session
-     * @throws RepositoryException if an error occurs
-     */
-    @Override
-    public Session login() throws RepositoryException {
-        return login(null, null, null);
-    }
-
-    /**
-     * Calls {@link Repository#login(Credentials, String)} with
-     * the given credentials and a {@code null} workspace name.
-     *
-     * @param credentials login credentials
-     * @return logged in session
-     * @throws RepositoryException if an error occurs
-     */
-    @Override
-    public Session login(Credentials credentials) throws RepositoryException {
-        return login(credentials, null, null);
-    }
-
-    /**
-     * Calls {@link Repository#login(Credentials, String)} with
-     * {@code null} credentials and the given workspace name.
-     *
-     * @param workspace workspace name
-     * @return logged in session
-     * @throws RepositoryException if an error occurs
-     */
-    @Override
-    public Session login(String workspace) throws RepositoryException {
-        return login(null, workspace, null);
-    }
-
-    //------------------------------------------------------------< 
JackrabbitRepository >---
-
-    @Override
-    public Session login(@CheckForNull Credentials credentials, @CheckForNull 
String workspaceName,
-            @CheckForNull Map<String, Object> attributes) throws 
RepositoryException {
-        try {
-            if (attributes == null) {
-                attributes = Collections.emptyMap();
-            }
-            Long refreshInterval = getRefreshInterval(credentials);
-            if (refreshInterval == null) {
-                refreshInterval = getRefreshInterval(attributes);
-            } else if (attributes.containsKey(REFRESH_INTERVAL)) {
-                throw new RepositoryException("Duplicate attribute '" + 
REFRESH_INTERVAL + "'.");
-            }
-
-            RefreshStrategy refreshStrategy = 
createRefreshStrategy(refreshInterval);
-            ContentSession contentSession = 
contentRepository.login(credentials, workspaceName);
-            SessionDelegate sessionDelegate = 
createSessionDelegate(refreshStrategy, contentSession);
-            SessionContext context = createSessionContext(
-                    statisticManager, securityProvider, 
createAttributes(refreshInterval),
-                    sessionDelegate, observationQueueLength, 
commitRateLimiter);
-            return context.getSession();
-        } catch (LoginException e) {
-            throw new javax.jcr.LoginException(e.getMessage(), e);
-        }
-    }
-
-    private SessionDelegate createSessionDelegate(
-            final RefreshStrategy refreshStrategy,
-            final ContentSession contentSession) {
-        return new SessionDelegate(
-                contentSession, securityProvider, refreshStrategy,
-                threadSaveCount, statisticManager, clock) {
-            // Defer session MBean registration to avoid cluttering the
-            // JMX name space with short lived sessions
-            ListenableScheduledFuture<Registration> registration = 
scheduledExecutor.schedule(
-                    new RegistrationCallable(getSessionStats(), whiteboard), 
1, TimeUnit.MINUTES);
-
-            @Override
-            public void logout() {
-                // Cancel session MBean registration and unregister MBean
-                // if registration succeed before the cancellation
-                registration.cancel(false);
-                Futures.addCallback(registration, new 
FutureCallback<Registration>() {
-                    @Override
-                    public void onSuccess(Registration registration) {
-                        registration.unregister();
-                    }
-
-                    @Override
-                    public void onFailure(Throwable t) {
-                    }
-                });
-
-                super.logout();
-            }
-        };
-    }
-
-    @Override
-    public void shutdown() {
-        statisticManager.dispose();
-        scheduledExecutor.shutdown();
-    }
-
-    //------------------------------------------------------------< internal 
>---
-
-    /**
-     * Factory method for creating a {@link SessionContext} instance for
-     * a new session. Called by {@link #login()}. Can be overridden by
-     * subclasses to customize the session implementation.
-     *
-     * @return session context
-     */
-    protected SessionContext createSessionContext(
-            StatisticManager statisticManager, SecurityProvider 
securityProvider,
-            Map<String, Object> attributes, SessionDelegate delegate, int 
observationQueueLength,
-            CommitRateLimiter commitRateLimiter) {
-        return new SessionContext(this, statisticManager, securityProvider, 
whiteboard, attributes,
-                delegate, observationQueueLength, commitRateLimiter);
-    }
-
-    /**
-     * Provides descriptors for current repository implementations. Can be 
overridden
-     * by the subclasses to add more values to the descriptor
-     * @return  repository descriptor
-     */
-    protected GenericDescriptors determineDescriptors() {
-        return new JcrDescriptorsImpl(contentRepository.getDescriptors(), new 
SimpleValueFactory());
-    }
-
-    /**
-     * Returns the descriptors associated with the repository
-     * @return repository descriptor
-     */
-    protected GenericDescriptors getDescriptors() {
-        return descriptors;
-    }
-
-//------------------------------------------------------------< private >---
-
-    private static Long getRefreshInterval(Credentials credentials) {
-        if (credentials instanceof SimpleCredentials) {
-            Object value = ((SimpleCredentials) 
credentials).getAttribute(REFRESH_INTERVAL);
-            return toLong(value);
-        } else if (credentials instanceof TokenCredentials) {
-            String value = ((TokenCredentials) 
credentials).getAttribute(REFRESH_INTERVAL);
-            if (value != null) {
-                return toLong(value);
-            }
-        }
-        return null;
-    }
-
-    private static Long getRefreshInterval(Map<String, Object> attributes) {
-        return toLong(attributes.get(REFRESH_INTERVAL));
-    }
-
-    private static Long toLong(Object value) {
-        if (value instanceof Long) {
-            return (Long) value;
-        } else if (value instanceof Integer) {
-            return ((Integer) value).longValue();
-        } else if (value instanceof String) {
-            return toLong((String) value);
-        } else {
-            return null;
-        }
-    }
-
-    private static Long toLong(String longValue) {
-        try {
-            return Long.valueOf(longValue);
-        } catch (NumberFormatException e) {
-            log.warn("Invalid value '" + longValue + "' for " + 
REFRESH_INTERVAL +
-                    ". Expected long. ", e);
-            return null;
-        }
-    }
-
-    private static Map<String, Object> createAttributes(Long refreshInterval) {
-        return refreshInterval == null
-                ? Collections.<String, Object>emptyMap()
-                : Collections.<String, Object>singletonMap(REFRESH_INTERVAL, 
refreshInterval);
-    }
-
-    /**
-     * Auto refresh logic for sessions, which is done to enhance backwards 
compatibility with
-     * Jackrabbit 2.
-     * <p>
-     * A sessions is automatically refreshed when
-     * <ul>
-     *     <li>it has not been accessed for the number of seconds specified by 
the
-     *         {@code refreshInterval} parameter,</li>
-     *     <li>an observation event has been delivered to a listener 
registered from within this
-     *         session,</li>
-     *     <li>an updated occurred through a different session from <em>within 
the same
-     *         thread.</em></li>
-     * </ul>
-     * In addition a warning is logged once per session if the session is 
accessed after one
-     * minute of inactivity.
-     */
-    private RefreshStrategy createRefreshStrategy(Long refreshInterval) {
-        if (refreshInterval == null) {
-            return new RefreshStrategy.LogOnce(60);
-        } else {
-            return new RefreshStrategy.Timed(refreshInterval);
-        }
-    }
-
-    private static class RegistrationCallable implements 
Callable<Registration> {
-        private final SessionStats sessionStats;
-        private final Whiteboard whiteboard;
-
-        public RegistrationCallable(SessionStats sessionStats, Whiteboard 
whiteboard) {
-            this.sessionStats = sessionStats;
-            this.whiteboard = whiteboard;
-        }
-
-        @Override
-        public Registration call() throws Exception {
-            return WhiteboardUtils.registerMBean(whiteboard, 
SessionMBean.class,
-                    sessionStats, SessionMBean.TYPE, sessionStats.toString());
-        }
-    }
-}
+/*
+ * 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.oak.jcr.repository;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.singletonMap;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.jcr.Credentials;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.Value;
+import javax.security.auth.login.LoginException;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableScheduledFuture;
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import org.apache.jackrabbit.api.JackrabbitRepository;
+import 
org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
+import org.apache.jackrabbit.commons.SimpleValueFactory;
+import org.apache.jackrabbit.oak.api.ContentRepository;
+import org.apache.jackrabbit.oak.api.ContentSession;
+import org.apache.jackrabbit.oak.api.jmx.SessionMBean;
+import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate;
+import org.apache.jackrabbit.oak.jcr.session.RefreshStrategy;
+import org.apache.jackrabbit.oak.jcr.session.SessionContext;
+import org.apache.jackrabbit.oak.jcr.session.SessionStats;
+import org.apache.jackrabbit.oak.plugins.observation.CommitRateLimiter;
+import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils;
+import org.apache.jackrabbit.oak.stats.Clock;
+import org.apache.jackrabbit.oak.stats.StatisticManager;
+import org.apache.jackrabbit.oak.util.GenericDescriptors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * TODO document
+ */
+public class RepositoryImpl implements JackrabbitRepository {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = 
LoggerFactory.getLogger(RepositoryImpl.class);
+
+    /**
+     * Name of the session attribute value determining the session refresh
+     * interval in seconds.
+     *
+     * @see org.apache.jackrabbit.oak.jcr.session.RefreshStrategy
+     */
+    public static final String REFRESH_INTERVAL = "oak.refresh-interval";
+
+    /**
+     * Name of the session attribute for enabling relaxed locking rules
+     *
+     * @see <a 
href="https://issues.apache.org/jira/browse/OAK-1329";>OAK-1329</a>
+     */
+    public static final String RELAXED_LOCKING = "oak.relaxed-locking";
+
+    private final GenericDescriptors descriptors;
+    private final ContentRepository contentRepository;
+    protected final Whiteboard whiteboard;
+    private final SecurityProvider securityProvider;
+    private final int observationQueueLength;
+    private final CommitRateLimiter commitRateLimiter;
+
+    private final Clock clock;
+
+    /**
+     * {@link ThreadLocal} counter that keeps track of the save operations
+     * performed per thread so far. This is is then used to determine if
+     * the current session needs to be refreshed to see the changes done by
+     * another session in the same thread.
+     * <p>
+     * <b>Note</b> - This thread local is never cleared. However, we only
+     * store a {@link Long} instance and do not derive from
+     * {@link ThreadLocal} so that (class loader) leaks typically associated
+     * with thread locals do not occur.
+     */
+    private final ThreadLocal<Long> threadSaveCount = new ThreadLocal<Long>();
+
+    private final ListeningScheduledExecutorService scheduledExecutor =
+            
MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor());
+    private final StatisticManager statisticManager;
+
+    public RepositoryImpl(@Nonnull ContentRepository contentRepository,
+                          @Nonnull Whiteboard whiteboard,
+                          @Nonnull SecurityProvider securityProvider,
+                          int observationQueueLength,
+                          CommitRateLimiter commitRateLimiter) {
+        this.contentRepository = checkNotNull(contentRepository);
+        this.whiteboard = checkNotNull(whiteboard);
+        this.securityProvider = checkNotNull(securityProvider);
+        this.observationQueueLength = observationQueueLength;
+        this.commitRateLimiter = commitRateLimiter;
+        this.descriptors = determineDescriptors();
+        this.statisticManager = new StatisticManager(whiteboard, 
scheduledExecutor);
+        this.clock = new Clock.Fast(scheduledExecutor);
+    }
+
+    //---------------------------------------------------------< Repository 
>---
+    /**
+     * @see javax.jcr.Repository#getDescriptorKeys()
+     */
+    @Override
+    public String[] getDescriptorKeys() {
+        return descriptors.getKeys();
+    }
+
+    /**
+     * @see Repository#isStandardDescriptor(String)
+     */
+    @Override
+    public boolean isStandardDescriptor(String key) {
+        return descriptors.isStandardDescriptor(key);
+    }
+
+    /**
+     * @see javax.jcr.Repository#getDescriptor(String)
+     */
+    @Override
+    public String getDescriptor(String key) {
+        try {
+            Value v = getDescriptorValue(key);
+            return v == null
+                    ? null
+                    : v.getString();
+        } catch (RepositoryException e) {
+            log.debug("Error converting value for descriptor with key {} to 
string", key);
+            return null;
+        }
+    }
+
+    /**
+     * @see javax.jcr.Repository#getDescriptorValue(String)
+     */
+    @Override
+    public Value getDescriptorValue(String key) {
+        return descriptors.getValue(key);
+    }
+
+    /**
+     * @see javax.jcr.Repository#getDescriptorValues(String)
+     */
+    @Override
+    public Value[] getDescriptorValues(String key) {
+        return descriptors.getValues(key);
+    }
+
+    /**
+     * @see javax.jcr.Repository#isSingleValueDescriptor(String)
+     */
+    @Override
+    public boolean isSingleValueDescriptor(String key) {
+        return descriptors.isSingleValueDescriptor(key);
+    }
+
+    /**
+     * @see javax.jcr.Repository#login(javax.jcr.Credentials, String)
+     */
+    @Override
+    public Session login(@Nullable Credentials credentials, @Nullable String 
workspaceName)
+            throws RepositoryException {
+        return login(credentials, workspaceName, null);
+    }
+
+    /**
+     * Calls {@link Repository#login(Credentials, String)} with
+     * {@code null} arguments.
+     *
+     * @return logged in session
+     * @throws RepositoryException if an error occurs
+     */
+    @Override
+    public Session login() throws RepositoryException {
+        return login(null, null, null);
+    }
+
+    /**
+     * Calls {@link Repository#login(Credentials, String)} with
+     * the given credentials and a {@code null} workspace name.
+     *
+     * @param credentials login credentials
+     * @return logged in session
+     * @throws RepositoryException if an error occurs
+     */
+    @Override
+    public Session login(Credentials credentials) throws RepositoryException {
+        return login(credentials, null, null);
+    }
+
+    /**
+     * Calls {@link Repository#login(Credentials, String)} with
+     * {@code null} credentials and the given workspace name.
+     *
+     * @param workspace workspace name
+     * @return logged in session
+     * @throws RepositoryException if an error occurs
+     */
+    @Override
+    public Session login(String workspace) throws RepositoryException {
+        return login(null, workspace, null);
+    }
+
+    //------------------------------------------------------------< 
JackrabbitRepository >---
+
+    @Override
+    public Session login(@CheckForNull Credentials credentials, @CheckForNull 
String workspaceName,
+            @CheckForNull Map<String, Object> attributes) throws 
RepositoryException {
+        try {
+            if (attributes == null) {
+                attributes = Collections.emptyMap();
+            }
+            Long refreshInterval = getRefreshInterval(credentials);
+            if (refreshInterval == null) {
+                refreshInterval = getRefreshInterval(attributes);
+            } else if (attributes.containsKey(REFRESH_INTERVAL)) {
+                throw new RepositoryException("Duplicate attribute '" + 
REFRESH_INTERVAL + "'.");
+            }
+            boolean relaxedLocking = getRelaxedLocking(attributes);
+
+            RefreshStrategy refreshStrategy = 
createRefreshStrategy(refreshInterval);
+            ContentSession contentSession = 
contentRepository.login(credentials, workspaceName);
+            SessionDelegate sessionDelegate = 
createSessionDelegate(refreshStrategy, contentSession);
+            SessionContext context = createSessionContext(
+                    statisticManager, securityProvider,
+                    createAttributes(refreshInterval, relaxedLocking),
+                    sessionDelegate, observationQueueLength, 
commitRateLimiter);
+            return context.getSession();
+        } catch (LoginException e) {
+            throw new javax.jcr.LoginException(e.getMessage(), e);
+        }
+    }
+
+    private SessionDelegate createSessionDelegate(
+            final RefreshStrategy refreshStrategy,
+            final ContentSession contentSession) {
+        return new SessionDelegate(
+                contentSession, securityProvider, refreshStrategy,
+                threadSaveCount, statisticManager, clock) {
+            // Defer session MBean registration to avoid cluttering the
+            // JMX name space with short lived sessions
+            ListenableScheduledFuture<Registration> registration = 
scheduledExecutor.schedule(
+                    new RegistrationCallable(getSessionStats(), whiteboard), 
1, TimeUnit.MINUTES);
+
+            @Override
+            public void logout() {
+                // Cancel session MBean registration and unregister MBean
+                // if registration succeed before the cancellation
+                registration.cancel(false);
+                Futures.addCallback(registration, new 
FutureCallback<Registration>() {
+                    @Override
+                    public void onSuccess(Registration registration) {
+                        registration.unregister();
+                    }
+
+                    @Override
+                    public void onFailure(Throwable t) {
+                    }
+                });
+
+                super.logout();
+            }
+        };
+    }
+
+    @Override
+    public void shutdown() {
+        statisticManager.dispose();
+        scheduledExecutor.shutdown();
+    }
+
+    //------------------------------------------------------------< internal 
>---
+
+    /**
+     * Factory method for creating a {@link SessionContext} instance for
+     * a new session. Called by {@link #login()}. Can be overridden by
+     * subclasses to customize the session implementation.
+     *
+     * @return session context
+     */
+    protected SessionContext createSessionContext(
+            StatisticManager statisticManager, SecurityProvider 
securityProvider,
+            Map<String, Object> attributes, SessionDelegate delegate, int 
observationQueueLength,
+            CommitRateLimiter commitRateLimiter) {
+        return new SessionContext(this, statisticManager, securityProvider, 
whiteboard, attributes,
+                delegate, observationQueueLength, commitRateLimiter);
+    }
+
+    /**
+     * Provides descriptors for current repository implementations. Can be 
overridden
+     * by the subclasses to add more values to the descriptor
+     * @return  repository descriptor
+     */
+    protected GenericDescriptors determineDescriptors() {
+        return new JcrDescriptorsImpl(contentRepository.getDescriptors(), new 
SimpleValueFactory());
+    }
+
+    /**
+     * Returns the descriptors associated with the repository
+     * @return repository descriptor
+     */
+    protected GenericDescriptors getDescriptors() {
+        return descriptors;
+    }
+
+//------------------------------------------------------------< private >---
+
+    private static Long getRefreshInterval(Credentials credentials) {
+        if (credentials instanceof SimpleCredentials) {
+            Object value = ((SimpleCredentials) 
credentials).getAttribute(REFRESH_INTERVAL);
+            return toLong(value);
+        } else if (credentials instanceof TokenCredentials) {
+            String value = ((TokenCredentials) 
credentials).getAttribute(REFRESH_INTERVAL);
+            if (value != null) {
+                return toLong(value);
+            }
+        }
+        return null;
+    }
+
+    private static Long getRefreshInterval(Map<String, Object> attributes) {
+        return toLong(attributes.get(REFRESH_INTERVAL));
+    }
+
+    private static boolean getRelaxedLocking(Map<String, Object> attributes) {
+        Object value = attributes.get(RELAXED_LOCKING);
+        if (value instanceof Boolean) {
+            return (Boolean) value;
+        } else if (value instanceof String) {
+            return Boolean.parseBoolean((String) value);
+        } else {
+            return false;
+        }
+    }
+
+    private static Long toLong(Object value) {
+        if (value instanceof Long) {
+            return (Long) value;
+        } else if (value instanceof Integer) {
+            return ((Integer) value).longValue();
+        } else if (value instanceof String) {
+            return toLong((String) value);
+        } else {
+            return null;
+        }
+    }
+
+    private static Long toLong(String longValue) {
+        try {
+            return Long.valueOf(longValue);
+        } catch (NumberFormatException e) {
+            log.warn("Invalid value '" + longValue + "' for " + 
REFRESH_INTERVAL +
+                    ". Expected long. ", e);
+            return null;
+        }
+    }
+
+    private static Map<String, Object> createAttributes(
+            Long refreshInterval, boolean relaxedLocking) {
+        if (refreshInterval == null && !relaxedLocking) {
+            return emptyMap();
+        } else if (refreshInterval == null) {
+            return singletonMap(RELAXED_LOCKING, (Object) 
Boolean.valueOf(relaxedLocking));
+        } else if (!relaxedLocking) {
+            return singletonMap(REFRESH_INTERVAL, (Object) refreshInterval);
+        } else {
+            return ImmutableMap.of(
+                    REFRESH_INTERVAL, (Object) refreshInterval,
+                    RELAXED_LOCKING,  (Object) 
Boolean.valueOf(relaxedLocking));
+        }
+    }
+
+    /**
+     * Auto refresh logic for sessions, which is done to enhance backwards 
compatibility with
+     * Jackrabbit 2.
+     * <p>
+     * A sessions is automatically refreshed when
+     * <ul>
+     *     <li>it has not been accessed for the number of seconds specified by 
the
+     *         {@code refreshInterval} parameter,</li>
+     *     <li>an observation event has been delivered to a listener 
registered from within this
+     *         session,</li>
+     *     <li>an updated occurred through a different session from <em>within 
the same
+     *         thread.</em></li>
+     * </ul>
+     * In addition a warning is logged once per session if the session is 
accessed after one
+     * minute of inactivity.
+     */
+    private RefreshStrategy createRefreshStrategy(Long refreshInterval) {
+        if (refreshInterval == null) {
+            return new RefreshStrategy.LogOnce(60);
+        } else {
+            return new RefreshStrategy.Timed(refreshInterval);
+        }
+    }
+
+    private static class RegistrationCallable implements 
Callable<Registration> {
+        private final SessionStats sessionStats;
+        private final Whiteboard whiteboard;
+
+        public RegistrationCallable(SessionStats sessionStats, Whiteboard 
whiteboard) {
+            this.sessionStats = sessionStats;
+            this.whiteboard = whiteboard;
+        }
+
+        @Override
+        public Registration call() throws Exception {
+            return WhiteboardUtils.registerMBean(whiteboard, 
SessionMBean.class,
+                    sessionStats, SessionMBean.TYPE, sessionStats.toString());
+        }
+    }
+}

Modified: 
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/NodeImpl.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/NodeImpl.java?rev=1581696&r1=1581695&r2=1581696&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/NodeImpl.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/NodeImpl.java
 Wed Mar 26 05:23:33 2014
@@ -54,6 +54,7 @@ import javax.jcr.UnsupportedRepositoryOp
 import javax.jcr.Value;
 import javax.jcr.lock.Lock;
 import javax.jcr.lock.LockException;
+import javax.jcr.lock.LockManager;
 import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.nodetype.NodeDefinition;
 import javax.jcr.nodetype.NodeType;
@@ -67,6 +68,7 @@ import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Lists;
+
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.api.JackrabbitNode;
 import org.apache.jackrabbit.commons.ItemNameMatcher;
@@ -1165,84 +1167,35 @@ public class NodeImpl<T extends NodeDele
         return getVersionManager().getBaseVersion(getPath());
     }
 
+    private LockManager getLockManager() throws RepositoryException {
+        return getSession().getWorkspace().getLockManager();
+    }
+
     @Override
     public boolean isLocked() throws RepositoryException {
-        return perform(new LockOperation<Boolean>(sessionDelegate, dlg, 
"isLocked") {
-            @Override
-            public Boolean perform(NodeDelegate node) {
-                return node.isLocked();
-            }
-        });
+        return getLockManager().isLocked(getPath());
     }
 
     @Override
     public boolean holdsLock() throws RepositoryException {
-        return perform(new LockOperation<Boolean>(sessionDelegate, dlg, 
"holdsLock") {
-            @Override
-            public Boolean perform(NodeDelegate node) {
-                return node.holdsLock(false);
-            }
-        });
+        return getLockManager().holdsLock(getPath());
     }
 
     @Override @Nonnull
     public Lock getLock() throws RepositoryException {
-        NodeDelegate lock = perform(
-                new LockOperation<NodeDelegate>(sessionDelegate, dlg, 
"getLock") {
-                    @Override
-                    public NodeDelegate perform(NodeDelegate node) {
-                        return node.getLock();
-                    }
-                });
-        if (lock != null) {
-            return new LockImpl(sessionContext, lock);
-        } else {
-            throw new LockException("Node " + getPath() + " is not locked");
-        }
+        return getLockManager().getLock(getPath());
     }
 
     @Override @Nonnull
-    public Lock lock(final boolean isDeep, final boolean isSessionScoped)
+    public Lock lock(boolean isDeep, boolean isSessionScoped)
             throws RepositoryException {
-        perform(new LockOperation<Void>(sessionDelegate, dlg, "lock") {
-            @Override
-            public Void perform(NodeDelegate node) throws RepositoryException {
-                if (node.getStatus() != Status.UNCHANGED) {
-                    throw new LockException(
-                            "Unable to lock a node with pending changes");
-                }
-                node.lock(isDeep);
-                String path = node.getPath();
-                if (isSessionScoped) {
-                    sessionContext.getSessionScopedLocks().add(path);
-                } else {
-                    sessionContext.getOpenScopedLocks().add(path);
-                }
-                session.refresh(true);
-                return null;
-            }
-        });
-        return new LockImpl(sessionContext, dlg);
+        return getLockManager().lock(
+                getPath(), isDeep, isSessionScoped, Long.MAX_VALUE, null);
     }
 
     @Override
     public void unlock() throws RepositoryException {
-        perform(new LockOperation<Void>(sessionDelegate, dlg, "unlock") {
-            @Override
-            public Void perform(NodeDelegate node) throws RepositoryException {
-                String path = node.getPath();
-                if (sessionContext.getSessionScopedLocks().contains(path)
-                        || sessionContext.getOpenScopedLocks().contains(path)) 
{
-                    node.unlock();
-                    sessionContext.getSessionScopedLocks().remove(path);
-                    sessionContext.getOpenScopedLocks().remove(path);
-                    session.refresh(true);
-                    return null;
-                } else {
-                    throw new LockException("Not an owner of the lock " + 
path);
-                }
-            }
-        });
+        getLockManager().unlock(getPath());
     }
 
     @Override @Nonnull


Reply via email to