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