code.onehippo.org is currently readonly. We are migrating to code.bloomreach.com, please continue working there on Monday 14/12. See: https://docs.bloomreach.com/display/engineering/GitLab

Commit cbf14aed authored by Ard Schrijvers's avatar Ard Schrijvers

REPO-1811 Support background jobs in the Memory or Db LockManager

- All the background jobs are run by a single thread
- By default they run every 5 seconds with an initial delay of 5 seconds
- Every background job is wrapped in a 'synchronized runnable' the synchronized
  on the LockManager instance: The reason for this is that we do not want
  background jobs to write concurrently to possibly the same records as
  other (background) jobs
- We have the following background jobs:
  1) UnlockStoppedThreadJanitor : releases database locks for records
     that were held by a thread that is not alive any more
  2) DbResetExpiredLocksJanitor : Resets all database rows which have
     expired locks (rows with status 'RUNNING' or 'ABORT' and expirationTime
     has passed
  3) DbLockRefresher: Refreshes the expiresTime of a lock to currentTime + refreshRateSeconds
  4) LockThreadInterrupter : Interrupts the Thread that holds the lock that has been marked 'ABORT'
parent 92c914ef
......@@ -46,7 +46,8 @@ import org.hippoecm.repository.jackrabbit.RepositoryImpl;
import org.hippoecm.repository.security.HippoSecurityManager;
import org.hippoecm.repository.util.RepoUtils;
import org.onehippo.repository.modules.ModuleManager;
import org.onehippo.services.lock.LockManagerFactory;
import org.onehippo.repository.lock.InternalLockManager;
import org.onehippo.repository.lock.LockManagerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -82,7 +83,7 @@ public class LocalHippoRepository extends HippoRepositoryImpl {
private String repoConfig;
private ConfigurationServiceImpl configurationService;
private LockManager lockManager;
private InternalLockManager lockManager;
private ModuleManager moduleManager;
......@@ -259,7 +260,7 @@ public class LocalHippoRepository extends HippoRepositoryImpl {
jackrabbitRepository = new LocalRepositoryImpl(createRepositoryConfig());
lockManager = new LockManagerFactory(jackrabbitRepository).create();
HippoServiceRegistry.registerService(lockManager, LockManager.class);
HippoServiceRegistry.registerService(lockManager, new Class[]{LockManager.class, InternalLockManager.class});
repository = new DecoratorFactoryImpl().getRepositoryDecorator(jackrabbitRepository);
final Session rootSession = jackrabbitRepository.getRootSession(null);
......
......@@ -13,16 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onehippo.services.lock;
package org.onehippo.repository.lock;
class MemoryLock extends AbstractLock {
import org.onehippo.cms7.services.lock.LockManager;
public MemoryLock(final String lockKey) {
super(lockKey, "default", Thread.currentThread().getName(), System.currentTimeMillis());
}
public interface InternalLockManager extends LockManager {
@Override
public void destroy() {
void destroy();
}
/**
* does similar logic as #destroy without closing background threads or marking the InternalLockManager as destroyed:
* This is useful / needed for integration tests which need to be able to validate certain behavior without destroying
* the InternalLockManager for real (since next test needs it again since repository is kept)
*/
void clear();
/**
* Adds a job to be scheduled. This method is part of this {@link InternalLockManager} because in integration tests
* we can then set the initialDelaySeconds and periodSeconds very low (for testing concurrency)
*/
void addJob(Runnable runnable, long initialDelaySeconds, long periodSeconds);
}
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onehippo.services.lock;
package org.onehippo.repository.lock;
import javax.jcr.RepositoryException;
import javax.sql.DataSource;
......@@ -22,6 +22,8 @@ import org.apache.jackrabbit.core.util.db.ConnectionHelper;
import org.apache.jackrabbit.core.util.db.ConnectionHelperDataSourceAccessor;
import org.hippoecm.repository.jackrabbit.RepositoryImpl;
import org.onehippo.cms7.services.lock.LockManager;
import org.onehippo.repository.lock.db.DbLockManager;
import org.onehippo.repository.lock.memory.MemoryLockManager;
public class LockManagerFactory {
......@@ -37,15 +39,15 @@ public class LockManagerFactory {
* @throws RuntimeException if the lock manager cannot be created, resulting the repository startup to short-circuit
* @throws RepositoryException if a repository exception happened while creating the lock manager
*/
public LockManager create() throws RuntimeException, RepositoryException {
public AbstractLockManager create() throws RuntimeException, RepositoryException {
final ConnectionHelper journalConnectionHelper = repositoryImpl.getJournalConnectionHelperAccessor().getConnectionHelper();
if (journalConnectionHelper != null) {
final DataSource dataSource = ConnectionHelperDataSourceAccessor.getDataSource(journalConnectionHelper);
String clusterNodeId = repositoryImpl.getDescriptor("jackrabbit.cluster.id");
return new AssertingLockManager(new DbLockManager(dataSource, clusterNodeId == null ? "default" : clusterNodeId));
return new DbLockManager(dataSource, clusterNodeId == null ? "default" : clusterNodeId);
} else {
return new AssertingLockManager(new MemoryLockManager());
return new MemoryLockManager();
}
}
}
......@@ -13,19 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onehippo.services.lock;
package org.onehippo.repository.lock;
import java.lang.ref.WeakReference;
import org.onehippo.cms7.services.lock.Lock;
public abstract class AbstractLock extends Lock {
public class MutableLock extends Lock {
private WeakReference<Thread> thread;
int holdCount;
public AbstractLock(final String lockKey, final String lockOwner, final String lockThread, final long creationTime) {
public MutableLock(final String lockKey, final String lockOwner, final String lockThread, final long creationTime) {
super(lockKey, lockOwner, lockThread, creationTime);
thread = new WeakReference<>(Thread.currentThread());
holdCount = 1;
......@@ -35,10 +35,6 @@ public abstract class AbstractLock extends Lock {
return thread;
}
public void setThread(final WeakReference<Thread> thread) {
this.thread = thread;
}
public void increment() {
holdCount++;
}
......@@ -51,5 +47,4 @@ public abstract class AbstractLock extends Lock {
return holdCount;
}
abstract void destroy();
}
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onehippo.services.lock.db;
package org.onehippo.repository.lock.db;
import java.sql.Connection;
import java.sql.ResultSet;
......@@ -36,7 +36,10 @@ public class DbHelper {
* @param dataSource
* @param tableName
*/
public static void createTableIfNeeded(final DataSource dataSource, final String createTableStatement, final String tableName) throws RuntimeException {
public static void createTableIfNeeded(final DataSource dataSource,
final String createTableStatement,
final String tableName,
final String... uniqueIndexes) throws RuntimeException {
try {
try (Connection connection = dataSource.getConnection()) {
final boolean tableExists = tableExists(connection, tableName);
......@@ -44,7 +47,9 @@ public class DbHelper {
log.info("Creating table {} ", tableName);
try (Statement statement = connection.createStatement()) {
statement.addBatch(String.format(createTableStatement, tableName));
statement.addBatch("CREATE UNIQUE INDEX " + tableName + "_idx_1 on " + tableName + "(lockkey)");
for (String uniqueIndex : uniqueIndexes) {
statement.addBatch("CREATE UNIQUE INDEX " + tableName + "_idx_1 on " + tableName + "("+uniqueIndex+")");
}
statement.setQueryTimeout(10);
statement.executeBatch();
} catch (SQLException e) {
......@@ -70,4 +75,17 @@ public class DbHelper {
return resultSet.next();
}
public static void close(final Connection connection, final boolean originalAutoCommit) {
if (connection == null) {
return;
}
try {
connection.setAutoCommit(originalAutoCommit);
connection.close();
} catch (SQLException e) {
log.error("Failed to close connection.", e);
}
}
}
/*
* Copyright 2017 Hippo B.V. (http://www.onehippo.com)
*
* Licensed 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.onehippo.repository.lock.db;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.onehippo.repository.lock.db.DbHelper.close;
import static org.onehippo.repository.lock.db.DbLockManager.LOCKS_TO_REFRESH_BLOCKING_STATEMENT;
import static org.onehippo.repository.lock.db.DbLockManager.REFRESH_LOCK_STATEMENT;
/**
* Refreshes all locks that are in possession by <strong>this</strong> cluster node and have less than 20 seconds to live. Note the 20
* seconds is a heuristic number: The {@link DbLockRefresher} runs about every 5 seconds. If some hiccup or other Threads
* causes some delay, 20 seconds should still be more than enough.
*/
public class DbLockRefresher implements Runnable {
private static final Logger log = LoggerFactory.getLogger(DbLockRefresher.class);
private final DataSource dataSource;
private final String clusterNodeId;
public DbLockRefresher(final DataSource dataSource, final String clusterNodeId) {
this.dataSource = dataSource;
this.clusterNodeId = clusterNodeId;
}
@Override
public void run() {
Connection connection = null;
boolean originalAutoCommit = false;
try {
connection = dataSource.getConnection();
originalAutoCommit = connection.getAutoCommit();
connection.setAutoCommit(false);
final PreparedStatement locksToRefreshStatement = connection.prepareStatement(LOCKS_TO_REFRESH_BLOCKING_STATEMENT);
locksToRefreshStatement.setString(1, clusterNodeId);
// select all rows that have less than 20 seconds to live
locksToRefreshStatement.setLong(1, System.currentTimeMillis() + 20000);
ResultSet resultSet = locksToRefreshStatement.executeQuery();
while (resultSet.next()) {
// found lock to refresh
final String lockKey = resultSet.getString("lockKey");
final int refreshRateSeconds = resultSet.getInt("refreshRateSeconds");
log.info("Refreshing row with lockKey '{}'", lockKey);
final PreparedStatement unlockStatement = connection.prepareStatement(REFRESH_LOCK_STATEMENT);
unlockStatement.setLong(1, System.currentTimeMillis() + refreshRateSeconds * 1000);
unlockStatement.setString(2, lockKey);
unlockStatement.execute();
}
connection.commit();
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.info("Exception in {} happened. Possibly another cluster node did already reset some lock rows:", this.getClass().getName(), e);
} else {
log.info("Exception in {} happened. Possibly another cluster node did already reset some lock rows: {}", this.getClass().getName(), e.toString());
}
} finally {
close(connection, originalAutoCommit);
}
}
}
/*
* Copyright 2017 Hippo B.V. (http://www.onehippo.com)
*
* Licensed 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.onehippo.repository.lock.db;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.onehippo.repository.lock.db.DbHelper.close;
import static org.onehippo.repository.lock.db.DbLockManager.EXPIRED_BLOCKING_STATEMENT;
import static org.onehippo.repository.lock.db.DbLockManager.RESET_LOCK_STATEMENT_BY_KEY_ONLY;
/**
* Resets expired locks to 'FREE' if they are in state 'RUNNING' or 'ABORT'
*/
public class DbResetExpiredLocksJanitor implements Runnable {
private static final Logger log = LoggerFactory.getLogger(DbResetExpiredLocksJanitor.class);
private final DataSource dataSource;
public DbResetExpiredLocksJanitor(final DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void run() {
Connection connection = null;
boolean originalAutoCommit = false;
try {
connection = dataSource.getConnection();
originalAutoCommit = connection.getAutoCommit();
connection.setAutoCommit(false);
// since the 'expired blocking statement' can modify the same rows as another cluster node, we use
// autocommit 'false' and use a blocking statement
final PreparedStatement expiredBlockingStatement = connection.prepareStatement(EXPIRED_BLOCKING_STATEMENT);
expiredBlockingStatement.setLong(1, System.currentTimeMillis());
ResultSet resultSet = expiredBlockingStatement.executeQuery();
while (resultSet.next()) {
// found expired lock. Reset it
String lockKey = resultSet.getString("lockKey");
log.info("Resetting row with lockKey '{}' because expired", lockKey);
final PreparedStatement resetLockStatement = connection.prepareStatement(RESET_LOCK_STATEMENT_BY_KEY_ONLY);
resetLockStatement.setString(1, lockKey);
resetLockStatement.execute();
}
connection.commit();
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.info("Exception in {} happened. Possibly another cluster node did already reset some lock rows:", this.getClass().getName(), e);
} else {
log.info("Exception in {} happened. Possibly another cluster node did already reset some lock rows: {}", this.getClass().getName(), e.toString());
}
} finally {
close(connection, originalAutoCommit);
}
}
}
/*
* Copyright 2017 Hippo B.V. (http://www.onehippo.com)
*
* Licensed 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.onehippo.repository.lock.db;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Map;
import javax.sql.DataSource;
import org.onehippo.repository.lock.MutableLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.onehippo.repository.lock.db.DbHelper.close;
import static org.onehippo.repository.lock.db.DbLockManager.TABLE_NAME_LOCK;
/**
* A Thread that contains a lock marked with 'ABORT' will be interrupted : In turn, that Thread should invoke #unlock itself
* If there is no Thread for the 'ABORT' marked lock, the lock will be reset to 'FREE'
*/
public class LockThreadInterrupter implements Runnable {
private static final Logger log = LoggerFactory.getLogger(LockThreadInterrupter.class);
private final DataSource dataSource;
private final String clusterNodeId;
private final Map<String, MutableLock> locks;
public static final String SELECT_ABORT_STATEMENT = "SELECT * FROM " + TABLE_NAME_LOCK + " WHERE status='ABORT' AND lockOwner=?";
public LockThreadInterrupter(final DataSource dataSource, final String clusterNodeId, final Map<String, MutableLock> locks) {
this.dataSource = dataSource;
this.clusterNodeId = clusterNodeId;
this.locks = locks;
}
public void run() {
Connection connection = null;
boolean originalAutoCommit = false;
try {
connection = dataSource.getConnection();
originalAutoCommit = connection.getAutoCommit();
connection.setAutoCommit(true);
final PreparedStatement selectAbortStatement = connection.prepareStatement(SELECT_ABORT_STATEMENT);
selectAbortStatement.setString(1, clusterNodeId);
ResultSet resultSet = selectAbortStatement.executeQuery();
while (resultSet.next()) {
// interrupt the thread for this lock (if still present). Otherwise ignore.
String lockKey = resultSet.getString("lockKey");
final String lockThread = resultSet.getString("lockThread");
if (lockThread == null) {
log.error("Cannot abort db entry '{}' for which lockThread is null.", lockKey);
continue;
}
boolean lockThreadForAbortFound = false;
for (MutableLock lock : locks.values()) {
Thread thread = lock.getThread().get();
if (thread == null || !thread.isAlive()) {
// ignore since will be picked up by org.onehippo.services.lock.AbstractLockManager.UnlockStoppedThreadJanitor
} else if (lockThread.equals(thread.getName()) && lockKey.equals(lock.getLockKey())){
// best effort : thread interrupt : As a result, the Thread containing the lock should invoke
// #unlock at some point in time
// There are no guarantees beyond best-effort attempts to stop
// processing actively executing Thread holding a lock. For example, typical
// implementations will cancel via {@link Thread#interrupt}, so any
// task that fails to respond to interrupts may never terminate.
try {
lockThreadForAbortFound = true;
log.info("Found Thread '{}' to be interrupted for Lock '{}'", thread.getName(), lockKey);
thread.interrupt();
} catch (SecurityException e) {
String msg = String.format("Thread '%s' is not allowed to be interrupted. Can't abort '%s'",
thread.getName(), lock.getLockKey());
log.error(msg);
}
}
}
if (!lockThreadForAbortFound) {
log.warn("There has not been found an alive Thread for key '{}' which has status 'ABORT'. After the " +
"lock has been expired it will be removed by DbLockResetJanitor", lockKey);
}
}
} catch (Exception e) {
log.error("Exception in {} happened:", this.getClass().getName(), e);
} finally {
close(connection, originalAutoCommit);
}
}
}
......@@ -13,24 +13,40 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onehippo.services.lock;
package org.onehippo.repository.lock.memory;
import org.onehippo.cms7.services.lock.LockException;
import org.onehippo.cms7.services.lock.LockManager;
import org.onehippo.repository.lock.AbstractLockManager;
import org.onehippo.repository.lock.MutableLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MemoryLockManager extends AbstractLockManager implements LockManager {
public class MemoryLockManager extends AbstractLockManager {
private static final Logger log = LoggerFactory.getLogger(MemoryLockManager.class);
public MemoryLockManager() {
addJob(new UnlockStoppedThreadJanitor());
}
@Override
Logger getLogger() {
protected Logger getLogger() {
return log;
}
@Override
AbstractLock createLock(final String key) throws LockException {
return new MemoryLock(key);
protected MutableLock createLock(final String key, final String threadName, final int refreshRateSeconds) throws LockException {
return new MutableLock(key, "default", threadName, System.currentTimeMillis());
}
@Override
protected void releasePersistedLock(final String key, final String threadName) throws LockException {
// no persistent lock needs to be removed so nothing to do
}
@Override
protected void abortPersistedLock(final String key) throws LockException {
// no persistent lock needs to be aborted so nothing else is needed
}
}
/*
* Copyright 2017 Hippo B.V. (http://www.onehippo.com)
*
* Licensed 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.onehippo.services.lock;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.onehippo.cms7.services.lock.Lock;
import org.onehippo.cms7.services.lock.LockException;
import org.onehippo.cms7.services.lock.LockManager;
import org.slf4j.Logger;
public abstract class AbstractLockManager implements LockManager {
private final Map<String, AbstractLock> locks = new HashMap();
abstract Logger getLogger();
abstract AbstractLock createLock(final String key) throws LockException;
@Override
public synchronized void lock(final String key) throws LockException {
final AbstractLock abstractLock = locks.get(key);
if (abstractLock == null) {
getLogger().debug("Create lock '{}' for thread '{}'", key, Thread.currentThread().getName());
locks.put(key, createLock(key));
return;
}
final Thread lockThread = abstractLock.getThread().get();
if (lockThread == null) {
getLogger().warn("Thread '{}' that created lock for '{}' has stopped without releasing the lock. Thread '{}' " +
"now gets the lock", abstractLock.getLockOwner(), key, Thread.currentThread().getName());
abstractLock.setThread(new WeakReference<>(Thread.currentThread()));
return;
}
if (lockThread == Thread.currentThread()) {
getLogger().debug("Thread '{}' already contains lock '{}', increase hold count", Thread.currentThread().getName(), key);
abstractLock.increment();
return;
}
throw new LockException(String.format("This thread '%s' cannot lock '%s' : already locked by thread '%s'",
Thread.currentThread().getName(), key, lockThread.getName()));
}
@Override
public synchronized void unlock(final String key) throws LockException {
final AbstractLock abstractLock = locks.get(key);
if (abstractLock == null) {
getLogger().debug("No lock present for '{}'", key);
return;
}
final Thread lockThread = abstractLock.getThread().get();
if (lockThread == null) {
getLogger().warn("Thread '{}' that created lock for '{}' has stopped without releasing the lock. Removing lock now",
abstractLock.getLockOwner(), key, Thread.currentThread().getName());
abstractLock.destroy();
locks.remove(key);
}
if (lockThread != Thread.currentThread()) {
throw new LockException(String.format("Thread '%s' cannot unlock '%s' because lock owned by '%s'", Thread.currentThread().getName(), key,
lockThread.getName()));
}
abstractLock.decrement();
if (abstractLock.holdCount < 0) {
getLogger().error("Hold count of lock should never be able to be less than 0. Core implementation issue in {}. Remove " +
"lock for {} nonetheless.",
this.getClass().getName(), key);
abstractLock.destroy();
locks.remove(key);
} else if (abstractLock.holdCount == 0) {
getLogger().debug("Remove lock '{}'", key);
abstractLock.destroy();
locks.remove(key);
} else {
getLogger().debug("Lock '{}' will not be removed since hold count is '{}'", key, abstractLock.holdCount);
}
}
@Override
public synchronized boolean isLocked(final String key) throws LockException {
expungeNeverUnlockedLocksFromGCedThreads();
return locks.containsKey(key);
}
@Override
public synchronized List<Lock> getLocks() {
expungeNeverUnlockedLocksFromGCedThreads();
return new ArrayList<>(locks.values());
}
@Override
public void destroy() {
Iterator<Map.Entry<String, AbstractLock>> iterator = locks.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, AbstractLock> next = iterator.next();
getLogger().warn("Lock '{}' owned by '{}' was never unlocked. Removing the lock now.", next.getKey(), next.getValue().getLockOwner());
next.getValue().destroy();
iterator.remove();
}
}
private void expungeNeverUnlockedLocksFromGCedThreads() {
Iterator<Map.Entry<String, AbstractLock>> iterator = locks.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, AbstractLock> next = iterator.next();
if (next.getValue().getThread().get() == null) {
getLogger().warn("Lock '{}' with lockOwner '{}' was present but the Thread that created the lock does not exist any more. " +
"Removing the lock now", next.getKey(), next.getValue().getLockOwner());
next.getValue().destroy();
iterator.remove();
}
}
}
}
/*
* Copyright 2017 Hippo B.V. (http://www.onehippo.com)
*
* Licensed 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.onehippo.services.lock;
import java.util.List;
import org.onehippo.cms7.services.lock.Lock;
import org.onehippo.cms7.services.lock.LockException;
import org.onehippo.cms7.services.lock.LockManager;
public class AssertingLockManager implements LockManager {
private LockManager delegatee;
public AssertingLockManager(final LockManager delegatee) {
this.delegatee = delegatee;
}
@Override
public void lock(final String key) throws LockException {
if (key == null || key.length() > 256) {
throw new IllegalArgumentException("Key is not allowed to be null or longer than 256 chars");
}
delegatee.lock(key);
}