diff --git a/config/alfresco/authority-services-context.xml b/config/alfresco/authority-services-context.xml
index 4cb5618f54..23ccb5d757 100644
--- a/config/alfresco/authority-services-context.xml
+++ b/config/alfresco/authority-services-context.xml
@@ -100,8 +100,8 @@
-
-
+
+
${authority.useBridgeTable}
diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml
index a2145f9cfb..d53fb654b4 100644
--- a/config/alfresco/cache-context.xml
+++ b/config/alfresco/cache-context.xml
@@ -146,11 +146,45 @@
-
-
-
-
+
+
+ asynchronouslyRefreshedCacheThreadPool
+
+
+ 1
+
+
+ 1
+
+
+ 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties
index e2e43e561d..f721a960d3 100644
--- a/config/alfresco/repository.properties
+++ b/config/alfresco/repository.properties
@@ -979,7 +979,6 @@ cache.immutableSingletonSharedCache.maxItems=12000
cache.remoteAlfrescoTicketService.ticketsCache.maxItems=1000
cache.contentDiskDriver.fileInfoCache.maxItems=1000
cache.globalConfigSharedCache.maxItems=1000
-cache.authorityBridgeTableByTenantSharedCache.maxItems=10
#
# Download Service Limits, in bytes
diff --git a/config/alfresco/tx-cache-context.xml b/config/alfresco/tx-cache-context.xml
index 01452ff1f9..0a75fedd9c 100644
--- a/config/alfresco/tx-cache-context.xml
+++ b/config/alfresco/tx-cache-context.xml
@@ -220,21 +220,8 @@
-
-
-
-
-
-
-
- org.alfresco.authorityBridgeTableByTenantTransactionalCache
-
-
-
-
-
-
-
+
+
diff --git a/source/java/org/alfresco/repo/cache/AbstractAsynchronouslyRefreshedCache.java b/source/java/org/alfresco/repo/cache/AbstractAsynchronouslyRefreshedCache.java
new file mode 100644
index 0000000000..271c96cd32
--- /dev/null
+++ b/source/java/org/alfresco/repo/cache/AbstractAsynchronouslyRefreshedCache.java
@@ -0,0 +1,802 @@
+/*
+ * Copyright (C) 2005-2013 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.cache;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.alfresco.repo.tenant.TenantService;
+import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
+import org.alfresco.repo.transaction.TransactionListener;
+import org.alfresco.util.PropertyCheck;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.InitializingBean;
+
+/**
+ * The base implementation for an asynchronously refreshed cache. Currently supports one value per tenant. Implementors
+ * just need to provide buildCache(String tenanaId)
+ *
+ * @author Andy
+ */
+public abstract class AbstractAsynchronouslyRefreshedCache implements AsynchronouslyRefreshedCache, RefreshableCacheListener, Callable, BeanNameAware,
+ InitializingBean, TransactionListener
+{
+ private static Log logger = LogFactory.getLog(AbstractAsynchronouslyRefreshedCache.class);
+
+ private static final String RESOURCE_KEY_TXN_DATA = "AbstractAsynchronouslyRefreshedCache.TxnData";
+
+ private List listeners = new LinkedList();
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.cache.AsynchronouslyRefreshedCacheRegistry#register(org.alfresco.repo.cache.
+ * RefreshableCacheListener)
+ */
+ @Override
+ public void register(RefreshableCacheListener listener)
+ {
+ listeners.add(listener);
+ }
+
+ private enum RefreshState
+ {
+ IDLE, WAITING, RUNNING, DONE
+ };
+
+ private ThreadPoolExecutor threadPoolExecutor;
+
+ private AsynchronouslyRefreshedCacheRegistry registry;
+
+ private TenantService tenantService;
+
+ // State
+
+ private final ReentrantReadWriteLock liveLock = new ReentrantReadWriteLock();
+
+ private final ReentrantReadWriteLock refreshLock = new ReentrantReadWriteLock();
+
+ private final ReentrantReadWriteLock runLock = new ReentrantReadWriteLock();
+
+ private HashMap live = new HashMap();
+
+ private LinkedHashSet refreshQueue = new LinkedHashSet();
+
+ private String cacheId;
+
+ private RefreshState refreshState = RefreshState.IDLE;
+
+ private String resourceKeyTxnData;
+
+ /**
+ * @param threadPool
+ * the threadPool to set
+ */
+ public void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor)
+ {
+ this.threadPoolExecutor = threadPoolExecutor;
+ }
+
+ /**
+ * @param registry
+ * the registry to set
+ */
+ public void setRegistry(AsynchronouslyRefreshedCacheRegistry registry)
+ {
+ this.registry = registry;
+ }
+
+ /**
+ * @param tenantService
+ * the tenantService to set
+ */
+ public void setTenantService(TenantService tenantService)
+ {
+ this.tenantService = tenantService;
+ }
+
+ public void init()
+ {
+ registry.register(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.cache.RefreshableCache#get()
+ */
+ @Override
+ public T get()
+ {
+ String tenantId = tenantService.getCurrentUserDomain();
+ liveLock.readLock().lock();
+ try
+ {
+ if (live.get(tenantId) != null)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("get() from cache");
+ }
+ return live.get(tenantId);
+ }
+ }
+ finally
+ {
+ liveLock.readLock().unlock();
+ }
+
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("get() miss, sechudling and waiting ...");
+ }
+
+ // There was nothing to return so we build and return
+ Refresh refresh = null;
+ refreshLock.writeLock().lock();
+ try
+ {
+ // Is there anything we can wait for
+ for (Refresh existing : refreshQueue)
+ {
+ if (existing.getTenantId().equals(tenantId))
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("get() found existing build to wait for ...");
+ }
+ refresh = existing;
+ }
+ }
+
+ if (refresh == null)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("get() building from scratch");
+ }
+ refresh = new Refresh(tenantId);
+ refreshQueue.add(refresh);
+ }
+
+ }
+ finally
+ {
+ refreshLock.writeLock().unlock();
+ }
+ submit();
+ waitForBuild(refresh);
+
+ return get();
+ }
+
+ public void forceInChangesForThisUncommittedTransaction()
+ {
+ String tenantId = tenantService.getCurrentUserDomain();
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Building cache for tenant" + tenantId + " ......");
+ }
+ T cache = buildCache(tenantId);
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(".... cache built for tenant" + tenantId);
+ }
+
+ liveLock.writeLock().lock();
+ try
+ {
+ live.put(tenantId, cache);
+ }
+ finally
+ {
+ liveLock.writeLock().unlock();
+ }
+ }
+
+ protected void waitForBuild(Refresh refresh)
+ {
+ while (refresh.getState() != RefreshState.DONE)
+ {
+ synchronized (refresh)
+ {
+ try
+ {
+ refresh.wait(100);
+ }
+ catch (InterruptedException e)
+ {
+ }
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.cache.RefreshableCache#refresh()
+ */
+ @Override
+ public void refresh()
+ {
+ String tenantId = tenantService.getCurrentUserDomain();
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Async cache refresh request: " + cacheId + " for tenant " + tenantId);
+ }
+ registry.broadcastEvent(new RefreshableCacheRefreshEvent(cacheId, tenantId), true);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.cache.RefreshableCacheListener#onRefreshableCacheEvent()
+ */
+ @Override
+ public void onRefreshableCacheEvent(RefreshableCacheEvent refreshableCacheEvent)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Async cache onRefreshableCacheEvent " + refreshableCacheEvent);
+ }
+ if (false == refreshableCacheEvent.getCacheId().equals(cacheId))
+ {
+ return;
+ }
+
+ // If in a transaction delay the refresh until after it commits
+
+ if (AlfrescoTransactionSupport.getTransactionId() != null)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Async cache adding" + refreshableCacheEvent.getTenantId() + " to post commit list");
+ }
+ TransactionData txData = getTransactionData();
+ txData.tenantIds.add(refreshableCacheEvent.getTenantId());
+ }
+ else
+ {
+ LinkedHashSet tenantIds = new LinkedHashSet();
+ tenantIds.add(refreshableCacheEvent.getTenantId());
+ queueRefreshAndSubmit(tenantIds);
+ }
+ }
+
+ /**
+ * To be used in a transaction only.
+ */
+ private TransactionData getTransactionData()
+ {
+ @SuppressWarnings("unchecked")
+ TransactionData data = (TransactionData) AlfrescoTransactionSupport.getResource(resourceKeyTxnData);
+ if (data == null)
+ {
+ data = new TransactionData();
+ // create and initialize caches
+ data.tenantIds = new LinkedHashSet();
+
+ // ensure that we get the transaction callbacks as we have bound the unique
+ // transactional caches to a common manager
+ AlfrescoTransactionSupport.bindListener(this);
+ AlfrescoTransactionSupport.bindResource(resourceKeyTxnData, data);
+ }
+ return data;
+ }
+
+ private void queueRefreshAndSubmit(LinkedHashSet tenantIds)
+ {
+ refreshLock.writeLock().lock();
+ try
+ {
+ for (String tenantId : tenantIds)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Async cache adding refresh to queue for "+tenantId);
+ }
+ refreshQueue.add(new Refresh(tenantId));
+ }
+ }
+ finally
+ {
+ refreshLock.writeLock().unlock();
+ }
+ submit();
+ }
+
+ /**
+ * @return
+ */
+ public boolean isUpToDate()
+ {
+ String tenantId = tenantService.getCurrentUserDomain();
+ refreshLock.readLock().lock();
+ try
+ {
+ for(Refresh refresh : refreshQueue)
+ {
+ if(refresh.getTenantId().equals(tenantId))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ finally
+ {
+ refreshLock.readLock().unlock();
+ }
+ }
+
+
+ // Must be run with runLock.writeLock
+ private Refresh getNextRefresh()
+ {
+ if (runLock.writeLock().isHeldByCurrentThread())
+ {
+ for (Refresh refresh : refreshQueue)
+ {
+ if (refresh.state == RefreshState.WAITING)
+ {
+ return refresh;
+ }
+ }
+ return null;
+ }
+ else
+ {
+ throw new IllegalStateException("Method should not be called without holding the write lock");
+ }
+
+ }
+
+ // Must be run with runLock.writeLock
+ private int countWaiting()
+ {
+ int count = 0;
+ if (runLock.writeLock().isHeldByCurrentThread())
+ {
+ refreshLock.readLock().lock();
+ try
+ {
+ for (Refresh refresh : refreshQueue)
+ {
+ if (refresh.state == RefreshState.WAITING)
+ {
+ count++;
+ }
+ }
+ return count;
+ }
+ finally
+ {
+ refreshLock.readLock().unlock();
+ }
+ }
+ else
+ {
+ throw new IllegalStateException("Method should not be called without holding the write lock");
+ }
+
+ }
+
+ private void submit()
+ {
+ runLock.writeLock().lock();
+ try
+ {
+ if (refreshState == RefreshState.IDLE)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("submit() scheduling job");
+ }
+ threadPoolExecutor.submit(this);
+ refreshState = RefreshState.WAITING;
+ }
+ }
+ finally
+ {
+ runLock.writeLock().unlock();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Runnable#run()
+ */
+ @Override
+ public Void call()
+ {
+ try
+ {
+ doCall();
+ return null;
+ }
+ catch (Exception e)
+ {
+ logger.warn("Cache update faiiled", e);
+ runLock.writeLock().lock();
+ try
+ {
+ threadPoolExecutor.submit(this);
+ refreshState = RefreshState.WAITING;
+ }
+ finally
+ {
+ runLock.writeLock().unlock();
+ }
+ return null;
+ }
+ }
+
+ /**
+ * @return
+ * @throws Exception
+ */
+ private void doCall() throws Exception
+ {
+ Refresh refresh = setUpRefresh();
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Building cache for tenant" + refresh.getTenantId());
+ }
+ if (refresh == null)
+ {
+ return;
+ }
+
+ try
+ {
+ doRefresh(refresh);
+ }
+ catch (Exception e)
+ {
+ refresh.setState(RefreshState.WAITING);
+ throw e;
+ }
+ }
+
+ /**
+ * @param refresh
+ * @return
+ */
+ private void doRefresh(Refresh refresh)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Building cache for tenant" + refresh.getTenantId() + " ......");
+ }
+ T cache = buildCache(refresh.getTenantId());
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(".... cache built for tenant" + refresh.getTenantId());
+ }
+
+ liveLock.writeLock().lock();
+ try
+ {
+ live.put(refresh.getTenantId(), cache);
+ }
+ finally
+ {
+ liveLock.writeLock().unlock();
+ }
+
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Cache entry updated for tenant" + refresh.getTenantId());
+ }
+
+ runLock.writeLock().lock();
+ try
+ {
+ refreshLock.writeLock().lock();
+ try
+ {
+ if (countWaiting() > 0)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Rescheduling ... more work");
+ }
+ threadPoolExecutor.submit(this);
+ refreshState = RefreshState.WAITING;
+ }
+ else
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Nothing to do .... going idle");
+ }
+ refreshState = RefreshState.IDLE;
+ }
+ refresh.setState(RefreshState.DONE);
+ refreshQueue.remove(refresh);
+ }
+ finally
+ {
+ refreshLock.writeLock().unlock();
+ }
+ }
+ finally
+ {
+ runLock.writeLock().unlock();
+ }
+ broadcastEvent(new RefreshableCacheRefreshedEvent(cacheId, refresh.tenantId));
+ }
+
+ private Refresh setUpRefresh() throws Exception
+ {
+ Refresh refresh = null;
+ runLock.writeLock().lock();
+ try
+ {
+ if (refreshState == RefreshState.WAITING)
+ {
+ refreshLock.writeLock().lock();
+ try
+ {
+ refresh = getNextRefresh();
+ if (refresh != null)
+ {
+ refreshState = RefreshState.RUNNING;
+ refresh.setState(RefreshState.RUNNING);
+ return refresh;
+ }
+ else
+ {
+ refreshState = RefreshState.IDLE;
+ return null;
+ }
+ }
+ finally
+ {
+ refreshLock.writeLock().unlock();
+ }
+ }
+ else
+ {
+ return null;
+ }
+ }
+ catch (Exception e)
+ {
+ if (refresh != null)
+ {
+ refresh.setState(RefreshState.WAITING);
+ }
+ throw e;
+ }
+ finally
+ {
+ runLock.writeLock().unlock();
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String)
+ */
+ @Override
+ public void setBeanName(String name)
+ {
+ cacheId = name;
+
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.cache.AsynchronouslyRefreshedCache#getCacheId()
+ */
+ @Override
+ public String getCacheId()
+ {
+ return cacheId;
+ }
+
+ /**
+ * Build the cache entry for the specific tenant.
+ *
+ * @param tenantId
+ * @return
+ */
+ protected abstract T buildCache(String tenantId);
+
+ private static class Refresh
+ {
+ private String tenantId;
+
+ private volatile RefreshState state = RefreshState.WAITING;
+
+ Refresh(String tenantId)
+ {
+ this.tenantId = tenantId;
+ }
+
+ /**
+ * @return the tenantId
+ */
+ public String getTenantId()
+ {
+ return tenantId;
+ }
+
+ /**
+ * @return the state
+ */
+ public RefreshState getState()
+ {
+ return state;
+ }
+
+ /**
+ * @param state
+ * the state to set
+ */
+ public void setState(RefreshState state)
+ {
+ this.state = state;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode()
+ {
+ // The bucked is determined by the tenantId alone - we are going to change the state
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Refresh other = (Refresh) obj;
+ if (state != other.state)
+ return false;
+ if (tenantId == null)
+ {
+ if (other.tenantId != null)
+ return false;
+ }
+ else if (!tenantId.equals(other.tenantId))
+ return false;
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString()
+ {
+ return "Refresh [tenantId=" + tenantId + ", state=" + state + ", hashCode()=" + hashCode() + "]";
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
+ */
+ @Override
+ public void afterPropertiesSet() throws Exception
+ {
+ PropertyCheck.mandatory(this, "threadPoolExecutor", threadPoolExecutor);
+ PropertyCheck.mandatory(this, "tenantService", tenantService);
+ PropertyCheck.mandatory(this, "registry", registry);
+ registry.register(this);
+
+ resourceKeyTxnData = RESOURCE_KEY_TXN_DATA + "." + cacheId;
+
+ }
+
+ public void broadcastEvent(RefreshableCacheEvent event)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Notifying cache listeners for " + getCacheId() + " " + event);
+ }
+ // If the system is up and running, broadcast the event immediately
+ for (RefreshableCacheListener listener : this.listeners)
+ {
+ listener.onRefreshableCacheEvent(event);
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.transaction.TransactionListener#flush()
+ */
+ @Override
+ public void flush()
+ {
+ // Nothing
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.transaction.TransactionListener#beforeCommit(boolean)
+ */
+ @Override
+ public void beforeCommit(boolean readOnly)
+ {
+ // Nothing
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.transaction.TransactionListener#beforeCompletion()
+ */
+ @Override
+ public void beforeCompletion()
+ {
+ // Nothing
+
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.transaction.TransactionListener#afterCommit()
+ */
+ @Override
+ public void afterCommit()
+ {
+ TransactionData txnData = getTransactionData();
+ queueRefreshAndSubmit(txnData.tenantIds);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.transaction.TransactionListener#afterRollback()
+ */
+ @Override
+ public void afterRollback()
+ {
+ // Nothing
+ }
+
+ private static class TransactionData
+ {
+ LinkedHashSet tenantIds;
+ }
+}
diff --git a/source/java/org/alfresco/repo/cache/AbstractRefreshableCacheEvent.java b/source/java/org/alfresco/repo/cache/AbstractRefreshableCacheEvent.java
new file mode 100644
index 0000000000..735fc10e4e
--- /dev/null
+++ b/source/java/org/alfresco/repo/cache/AbstractRefreshableCacheEvent.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2005-2013 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.cache;
+
+/**
+ * A generic event with the cache id and affected tenant
+ *
+ * @author Andy
+ */
+public abstract class AbstractRefreshableCacheEvent implements RefreshableCacheEvent
+{
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1324638640132648062L;
+
+ private String cacheId;
+
+ private String tenantId;
+
+ AbstractRefreshableCacheEvent(String cacheId, String tenantId)
+ {
+ this.cacheId = cacheId;
+ this.tenantId = tenantId;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.cache.RefreshableCacheEvent#getCacheId()
+ */
+ @Override
+ public String getCacheId()
+ {
+ return cacheId;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.cache.RefreshableCacheEvent#getTenantId()
+ */
+ @Override
+ public String getTenantId()
+ {
+ return tenantId;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString()
+ {
+ return "AbstractRefreshableCacheEvent [cacheId=" + cacheId + ", tenantId=" + tenantId + "]";
+ }
+
+
+}
diff --git a/source/java/org/alfresco/repo/cache/AsynchronouslyRefreshedCache.java b/source/java/org/alfresco/repo/cache/AsynchronouslyRefreshedCache.java
new file mode 100644
index 0000000000..3f93abfdc6
--- /dev/null
+++ b/source/java/org/alfresco/repo/cache/AsynchronouslyRefreshedCache.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2005-2013 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.cache;
+
+/**
+ * Implementation details in addition to the exposed interface.
+ *
+ * @author Andy
+ *
+ */
+public interface AsynchronouslyRefreshedCache extends RefreshableCache
+{
+
+ /**
+ * Get the cache id
+ *
+ * @return
+ */
+ String getCacheId();
+
+}
diff --git a/source/java/org/alfresco/repo/cache/AsynchronouslyRefreshedCacheRegistry.java b/source/java/org/alfresco/repo/cache/AsynchronouslyRefreshedCacheRegistry.java
new file mode 100644
index 0000000000..991d599c40
--- /dev/null
+++ b/source/java/org/alfresco/repo/cache/AsynchronouslyRefreshedCacheRegistry.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2005-2013 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.cache;
+
+/**
+ * A registry of all AsynchronouslyRefreshedCaches to be used for notification.
+ *
+ * @author Andy
+ *
+ */
+public interface AsynchronouslyRefreshedCacheRegistry
+{
+ /**
+ * Register a listener
+ * @param listener
+ */
+ public void register(RefreshableCacheListener listener);
+
+ /**
+ * Fire an even
+ * @param event
+ * @param toAll - true goes to all listeners, false only to listeners that have a matching cacheId
+ */
+ public void broadcastEvent(RefreshableCacheEvent event, boolean toAll);
+}
diff --git a/source/java/org/alfresco/repo/cache/DefaultAsynchronouslyRefreshedCacheRegistry.java b/source/java/org/alfresco/repo/cache/DefaultAsynchronouslyRefreshedCacheRegistry.java
new file mode 100644
index 0000000000..b5ecee5e30
--- /dev/null
+++ b/source/java/org/alfresco/repo/cache/DefaultAsynchronouslyRefreshedCacheRegistry.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2005-2013 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.cache;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Base registry implementation
+ * (Should be genericised somewhere..)
+ *
+ * @author Andy
+ */
+public class DefaultAsynchronouslyRefreshedCacheRegistry implements AsynchronouslyRefreshedCacheRegistry
+{
+ private static Log logger = LogFactory.getLog(DefaultAsynchronouslyRefreshedCacheRegistry.class);
+
+ private List listeners = new LinkedList();
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.cache.AsynchronouslyRefreshedCacheRegistry#register(org.alfresco.repo.cache.
+ * RefreshableCacheListener)
+ */
+ @Override
+ public void register(RefreshableCacheListener listener)
+ {
+ if(logger.isDebugEnabled())
+ {
+ logger.debug("Listener added for "+listener.getCacheId());
+ }
+ listeners.add(listener);
+ }
+
+ public void broadcastEvent(RefreshableCacheEvent event, boolean toAll)
+ {
+ // If the system is up and running, broadcast the event immediately
+
+ for (RefreshableCacheListener listener : this.listeners)
+ {
+ if (toAll)
+ {
+ if(logger.isDebugEnabled())
+ {
+ logger.debug("Delivering to "+listener);
+ }
+ listener.onRefreshableCacheEvent(event);
+ }
+ else
+ {
+ if (listener.getCacheId().equals(event.getCacheId()))
+ {
+ if(logger.isDebugEnabled())
+ {
+ logger.debug("Specific Delivery to "+listener);
+ }
+ listener.onRefreshableCacheEvent(event);
+ }
+ }
+ }
+
+ }
+
+}
diff --git a/source/java/org/alfresco/repo/cache/RefreshableCache.java b/source/java/org/alfresco/repo/cache/RefreshableCache.java
new file mode 100644
index 0000000000..8ece35bf93
--- /dev/null
+++ b/source/java/org/alfresco/repo/cache/RefreshableCache.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2005-2013 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.cache;
+
+/**
+ * Basic cache API
+ *
+ * @author Andy
+ *
+ */
+public interface RefreshableCache
+{
+ /**
+ * Get the cache.
+ * If there is no cache value this call will block.
+ * If the underlying cache is being refreshed, the old cache value will be returned until the refresh is complete.
+ *
+ * @return
+ */
+ public T get();
+
+ /**
+ * Refresh the cache asynchronously.
+ */
+ public void refresh();
+
+ /**
+ * Register to be informed when the cache is updated in the background.
+ *
+ * Note: it is up to the implementation to provide any transactional wrapping.
+ * Transactional wrapping is not required to invalidate a shared cache entry directly via a transactional cache
+ * @param listener
+ */
+ void register(RefreshableCacheListener listener);
+}
diff --git a/source/java/org/alfresco/repo/cache/RefreshableCacheEvent.java b/source/java/org/alfresco/repo/cache/RefreshableCacheEvent.java
new file mode 100644
index 0000000000..a742c40115
--- /dev/null
+++ b/source/java/org/alfresco/repo/cache/RefreshableCacheEvent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2005-2013 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.cache;
+
+import java.io.Serializable;
+
+/**
+ * A cache event
+ *
+ * @author Andy
+ *
+ */
+public interface RefreshableCacheEvent extends Serializable
+{
+ /**
+ * Get the cache id
+ * @return
+ */
+ public String getCacheId();
+
+ /**
+ * Get the affected tenant id
+ * @return
+ */
+ public String getTenantId();
+}
diff --git a/source/java/org/alfresco/repo/cache/RefreshableCacheListener.java b/source/java/org/alfresco/repo/cache/RefreshableCacheListener.java
new file mode 100644
index 0000000000..378de127bc
--- /dev/null
+++ b/source/java/org/alfresco/repo/cache/RefreshableCacheListener.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2005-2013 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.cache;
+
+/**
+ * API to listen to cache events
+ *
+ * @author Andy
+ *
+ */
+public interface RefreshableCacheListener
+{
+ /**
+ * Receive an event
+ * @param refreshableCacheEvent
+ */
+ public void onRefreshableCacheEvent(RefreshableCacheEvent refreshableCacheEvent);
+
+ /**
+ * Cache id so broadcast can be constrained to matching caches
+ * @return
+ */
+ public String getCacheId();
+}
diff --git a/source/java/org/alfresco/repo/cache/RefreshableCacheRefreshEvent.java b/source/java/org/alfresco/repo/cache/RefreshableCacheRefreshEvent.java
new file mode 100644
index 0000000000..e90392aff9
--- /dev/null
+++ b/source/java/org/alfresco/repo/cache/RefreshableCacheRefreshEvent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2005-2013 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.cache;
+
+/**
+ * Describes an entry that is stale in the cache
+ *
+ * @author Andy
+ *
+ */
+public class RefreshableCacheRefreshEvent extends AbstractRefreshableCacheEvent
+{
+ /**
+ * @param cacheId
+ */
+ RefreshableCacheRefreshEvent(String cacheId, String tenantId)
+ {
+ super(cacheId, tenantId);
+ }
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = -8011932788039835334L;
+
+}
diff --git a/source/java/org/alfresco/repo/cache/RefreshableCacheRefreshedEvent.java b/source/java/org/alfresco/repo/cache/RefreshableCacheRefreshedEvent.java
new file mode 100644
index 0000000000..9ce26bc52c
--- /dev/null
+++ b/source/java/org/alfresco/repo/cache/RefreshableCacheRefreshedEvent.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2005-2013 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.cache;
+
+/**
+ * Describes a new entry has been inserted in the cache.
+ *
+ * @author Andy
+ *
+ */
+public class RefreshableCacheRefreshedEvent extends AbstractRefreshableCacheEvent
+{
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 2352511592269578075L;
+
+ /**
+ * @param cacheId
+ * @param tenantId
+ */
+ RefreshableCacheRefreshedEvent(String cacheId, String tenantId)
+ {
+ super(cacheId, tenantId);
+ }
+
+}
diff --git a/source/java/org/alfresco/repo/cache/TransactionalCache.java b/source/java/org/alfresco/repo/cache/TransactionalCache.java
index b0654eeca7..c41e83d163 100644
--- a/source/java/org/alfresco/repo/cache/TransactionalCache.java
+++ b/source/java/org/alfresco/repo/cache/TransactionalCache.java
@@ -258,8 +258,15 @@ public class TransactionalCache
public boolean getDisableSharedCacheReadForTransaction()
{
- TransactionData txnData = getTransactionData();
- return txnData.noSharedCacheRead;
+ if (AlfrescoTransactionSupport.getTransactionId() != null)
+ {
+ TransactionData txnData = getTransactionData();
+ return txnData.noSharedCacheRead;
+ }
+ else
+ {
+ return false;
+ }
}
/**
diff --git a/source/java/org/alfresco/repo/security/authority/AbstractAuthorityBridgeDAO.java b/source/java/org/alfresco/repo/security/authority/AbstractAuthorityBridgeDAO.java
index e422d26bc1..31eead38ee 100644
--- a/source/java/org/alfresco/repo/security/authority/AbstractAuthorityBridgeDAO.java
+++ b/source/java/org/alfresco/repo/security/authority/AbstractAuthorityBridgeDAO.java
@@ -144,7 +144,7 @@ public abstract class AbstractAuthorityBridgeDAO implements AuthorityBridgeDAO
storeId = nodeDAO.getStore(tenantSpecificStoreRef).getFirst();
}
- Pair pair = (authRef == null) ? null : nodeDAO.getNodePair(authRef);
+ Pair pair = (authRef == null) ? null : nodeDAO.getNodePair(tenantService.getName(authRef));
return selectDirectAuthoritiesForUser(authorityContainerTypeQNameId, memberAssocQNameId, authorityNameQNameId, storeId, (pair == null) ? -1L : pair.getFirst());
}
diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityBridgeTableAsynchronouslyRefreshedCache.java b/source/java/org/alfresco/repo/security/authority/AuthorityBridgeTableAsynchronouslyRefreshedCache.java
new file mode 100644
index 0000000000..2529044ec4
--- /dev/null
+++ b/source/java/org/alfresco/repo/security/authority/AuthorityBridgeTableAsynchronouslyRefreshedCache.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2005-2013 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.security.authority;
+
+import java.util.List;
+
+import org.alfresco.repo.cache.AbstractAsynchronouslyRefreshedCache;
+import org.alfresco.repo.transaction.RetryingTransactionHelper;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.util.BridgeTable;
+import org.alfresco.util.PropertyCheck;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.InitializingBean;
+
+/**
+ * @author Andy
+ */
+public class AuthorityBridgeTableAsynchronouslyRefreshedCache extends AbstractAsynchronouslyRefreshedCache> implements InitializingBean
+{
+ private static Log logger = LogFactory.getLog(AuthorityBridgeTableAsynchronouslyRefreshedCache.class);
+
+ private AuthorityBridgeDAO authorityBridgeDAO;
+
+ private RetryingTransactionHelper retryingTransactionHelper;
+
+ /**
+ * @param authorityBridgeDAO
+ * the authorityBridgeDAO to set
+ */
+ public void setAuthorityBridgeDAO(AuthorityBridgeDAO authorityBridgeDAO)
+ {
+ this.authorityBridgeDAO = authorityBridgeDAO;
+ }
+
+ /**
+ * @param retryingTransactionHelper
+ * the retryingTransactionHelper to set
+ */
+ public void setRetryingTransactionHelper(RetryingTransactionHelper retryingTransactionHelper)
+ {
+ this.retryingTransactionHelper = retryingTransactionHelper;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.cache.AbstractAsynchronouslyRefreshedCache#buildCache()
+ */
+ @Override
+ protected BridgeTable buildCache(final String tenantId)
+ {
+ return retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback>()
+ {
+ @Override
+ public BridgeTable execute() throws Throwable
+ {
+ return doBuildCache(tenantId);
+ }
+ }, true, false);
+ }
+
+ private BridgeTable doBuildCache(String tenantId)
+ {
+ List links = authorityBridgeDAO.getAuthorityBridgeLinks();
+ BridgeTable bridgeTable = new BridgeTable();
+ for (AuthorityBridgeLink link : links)
+ {
+ bridgeTable.addLink(link.getParentName(), link.getChildName());
+ }
+ return bridgeTable;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.cache.AbstractAsynchronouslyRefreshedCache#afterPropertiesSet()
+ */
+ @Override
+ public void afterPropertiesSet() throws Exception
+ {
+ PropertyCheck.mandatory(this, "authorityBridgeDAO", authorityBridgeDAO);
+ PropertyCheck.mandatory(this, "retryingTransactionHelper", retryingTransactionHelper);
+ super.afterPropertiesSet();
+ }
+
+}
diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java
index db3f9483c9..0a547a0e3f 100644
--- a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java
+++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java
@@ -40,6 +40,8 @@ import org.alfresco.query.CannedQueryFactory;
import org.alfresco.query.CannedQueryResults;
import org.alfresco.query.PagingRequest;
import org.alfresco.query.PagingResults;
+import org.alfresco.repo.cache.RefreshableCacheEvent;
+import org.alfresco.repo.cache.RefreshableCacheListener;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.domain.permissions.AclDAO;
import org.alfresco.repo.node.NodeServicePolicies;
@@ -76,12 +78,14 @@ import org.alfresco.util.EqualsHelper;
import org.alfresco.util.ISO9075;
import org.alfresco.util.Pair;
import org.alfresco.util.ParameterCheck;
+import org.alfresco.util.PropertyCheck;
import org.alfresco.util.SearchLanguageConversion;
import org.alfresco.util.registry.NamedObjectRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.InitializingBean;
-public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.BeforeDeleteNodePolicy, NodeServicePolicies.OnUpdatePropertiesPolicy
+public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.BeforeDeleteNodePolicy, NodeServicePolicies.OnUpdatePropertiesPolicy, RefreshableCacheListener, InitializingBean
{
private static Log logger = LogFactory.getLog(AuthorityDAOImpl.class);
@@ -113,7 +117,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
private SimpleCache> userAuthorityCache;
private SimpleCache, List> zoneAuthorityCache;
private SimpleCache, List>> childAuthorityCache;
- private SimpleCache> authorityBridgeTableByTenantCache;
+ private AuthorityBridgeTableAsynchronouslyRefreshedCache authorityBridgeTableCache;
/** System Container ref cache (Tennant aware) */
private Map systemContainerRefs = new ConcurrentHashMap(4);
@@ -126,11 +130,14 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
private boolean useBridgeTable = true;
+ private boolean useGetContainingAuthoritiesForIsAuthorityContained = true;
+
private AclDAO aclDao;
private PolicyComponent policyComponent;
- private NamedObjectRegistry cannedQueryRegistry;
+ private NamedObjectRegistry> cannedQueryRegistry;
private AuthorityBridgeDAO authorityBridgeDAO;
+
public AuthorityDAOImpl()
{
super();
@@ -197,9 +204,9 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
this.childAuthorityCache = childAuthorityCache;
}
- public void setAuthorityBridgeTableByTenantCache(SimpleCache> authorityBridgeTableByTenantCache)
+ public void setAuthorityBridgeTableCache(AuthorityBridgeTableAsynchronouslyRefreshedCache authorityBridgeTableCache)
{
- this.authorityBridgeTableByTenantCache = authorityBridgeTableByTenantCache;
+ this.authorityBridgeTableCache = authorityBridgeTableCache;
}
/**
@@ -231,11 +238,19 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
this.policyComponent = policyComponent;
}
- public void setCannedQueryRegistry(NamedObjectRegistry cannedQueryRegistry)
+ public void setCannedQueryRegistry(NamedObjectRegistry> cannedQueryRegistry)
{
this.cannedQueryRegistry = cannedQueryRegistry;
}
+ /**
+ * @param useGetContainingAuthoritiesForHasAuthority the useGetContainingAuthoritiesForHasAuthority to set
+ */
+ public void setUseGetContainingAuthoritiesForIsAuthorityContained(boolean useGetContainingAuthoritiesForIsAuthorityContained)
+ {
+ this.useGetContainingAuthoritiesForIsAuthorityContained = useGetContainingAuthoritiesForIsAuthorityContained;
+ }
+
/**
* @param authorityBridgeDAO the authorityBridgeDAO to set
*/
@@ -296,7 +311,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
else
{
userAuthorityCache.clear();
- authorityBridgeTableByTenantCache.clear();
+ authorityBridgeTableCache.refresh();
}
}
@@ -352,7 +367,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
removeParentsFromChildAuthorityCache(nodeRef);
authorityLookupCache.remove(cacheKey(name));
userAuthorityCache.clear();
- authorityBridgeTableByTenantCache.clear();
+ authorityBridgeTableCache.refresh();
nodeService.deleteNode(nodeRef);
}
@@ -724,30 +739,14 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
else
{
userAuthorityCache.clear();
- authorityBridgeTableByTenantCache.clear();
+ authorityBridgeTableCache.refresh();
}
}
- private BridgeTable getBridgeTable()
- {
- String tenant = tenantService.getCurrentUserDomain();
- BridgeTable bridgeTable = authorityBridgeTableByTenantCache.get(tenant);
- if(bridgeTable == null)
- {
- List links = authorityBridgeDAO.getAuthorityBridgeLinks();
- bridgeTable = new BridgeTable();
- for(AuthorityBridgeLink link : links)
- {
- bridgeTable.addLink(link.getParentName(), link.getChildName());
- }
- authorityBridgeTableByTenantCache.put(tenant, bridgeTable);
- }
- return bridgeTable;
- }
-
+
private void listAuthoritiesByBridgeTable(Set authorities, String name)
{
- BridgeTable bridgeTable = getBridgeTable();
+ BridgeTable bridgeTable = authorityBridgeTableCache.get();
AuthorityType type = AuthorityType.getAuthorityType(name);
switch(type)
@@ -1095,8 +1094,21 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
negativeHits.add(getPooledName(authority));
return false;
}
-
- return isAuthorityContained(authorityNodeRef, getPooledName(authority), authorityToFind, positiveHits, negativeHits);
+ if(useGetContainingAuthoritiesForIsAuthorityContained)
+ {
+ if(authorityBridgeTableCache.isUpToDate())
+ {
+ return getContainingAuthorities(null, authorityToFind, false).contains(authority);
+ }
+ else
+ {
+ return isAuthorityContained(authorityNodeRef, getPooledName(authority), authorityToFind, positiveHits, negativeHits);
+ }
+ }
+ else
+ {
+ return isAuthorityContained(authorityNodeRef, getPooledName(authority), authorityToFind, positiveHits, negativeHits);
+ }
}
private boolean isAuthorityContained(NodeRef authorityNodeRef, String authority, String authorityToFind, Set positiveHits, Set negativeHits)
@@ -1492,7 +1504,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
}
}
authorityLookupCache.clear();
- authorityBridgeTableByTenantCache.clear();
+ authorityBridgeTableCache.refresh();
// Cache is out of date
userAuthorityCache.clear();
@@ -1621,5 +1633,57 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
}
return auths;
}
+ }
+
+ /* (non-Javadoc)
+ * @see org.alfresco.repo.cache.RefreshableCacheListener#onRefreshableCacheEvent(org.alfresco.repo.cache.RefreshableCacheEvent)
+ */
+ @Override
+ public void onRefreshableCacheEvent(RefreshableCacheEvent refreshableCacheEvent)
+ {
+ if(logger.isDebugEnabled())
+ {
+ logger.debug("Bridge Table cache triggering userAuthorityCache.clear()");
+ }
+ userAuthorityCache.clear();
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.alfresco.repo.cache.RefreshableCacheListener#getCacheId()
+ */
+ @Override
+ public String getCacheId()
+ {
+ return AuthorityDAOImpl.class.getName();
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
+ */
+ @Override
+ public void afterPropertiesSet() throws Exception
+ {
+ PropertyCheck.mandatory(this, "aclDao", aclDao);
+ PropertyCheck.mandatory(this, "authorityBridgeDAO", authorityBridgeDAO);
+ PropertyCheck.mandatory(this, "authorityBridgeTableCache", authorityBridgeTableCache);
+ PropertyCheck.mandatory(this, "authorityLookupCache", authorityLookupCache);
+ PropertyCheck.mandatory(this, "cannedQueryRegistry", cannedQueryRegistry);
+ PropertyCheck.mandatory(this, "childAuthorityCache", childAuthorityCache);
+ PropertyCheck.mandatory(this, "dictionaryService", dictionaryService);
+ PropertyCheck.mandatory(this, "namespacePrefixResolver", namespacePrefixResolver);
+ PropertyCheck.mandatory(this, "nodeService", nodeService);
+ PropertyCheck.mandatory(this, "personService", personService);
+ PropertyCheck.mandatory(this, "policyComponent", policyComponent);
+ PropertyCheck.mandatory(this, "searchService", searchService);
+ PropertyCheck.mandatory(this, "storeRef", storeRef);
+ PropertyCheck.mandatory(this, "tenantService", tenantService);
+ PropertyCheck.mandatory(this, "userAuthorityCache", userAuthorityCache);
+ PropertyCheck.mandatory(this, "zoneAuthorityCache", zoneAuthorityCache);
+ PropertyCheck.mandatory(this, "storeRef", storeRef);
+ PropertyCheck.mandatory(this, "storeRef", storeRef);
+ authorityBridgeTableCache.register(this);
+
};
}
diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java b/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java
index 6330f6f96a..43e11b5b3c 100644
--- a/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java
+++ b/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java
@@ -74,6 +74,7 @@ public class AuthorityServiceTest extends TestCase
private UserTransaction tx;
private AclDAO aclDaoComponent;
private NodeService nodeService;
+ private AuthorityBridgeTableAsynchronouslyRefreshedCache authorityBridgeTableCache;
public AuthorityServiceTest()
{
@@ -105,6 +106,7 @@ public class AuthorityServiceTest extends TestCase
authenticationDAO = (MutableAuthenticationDao) ctx.getBean("authenticationDao");
aclDaoComponent = (AclDAO) ctx.getBean("aclDAO");
nodeService = (NodeService) ctx.getBean("nodeService");
+ authorityBridgeTableCache = (AuthorityBridgeTableAsynchronouslyRefreshedCache) ctx.getBean("authorityBridgeTableCache");
String defaultAdminUser = AuthenticationUtil.getAdminUserName();
AuthenticationUtil.setFullyAuthenticatedUser(defaultAdminUser);
@@ -623,16 +625,19 @@ public class AuthorityServiceTest extends TestCase
assertEquals(ROOT_GRP_CNT+2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "three");
pubAuthorityService.addAuthority(auth1, auth3);
+ authorityBridgeTableCache.forceInChangesForThisUncommittedTransaction();
assertEquals("GROUP_three", auth3);
assertEquals(GRP_CNT+3, getAllAuthorities(AuthorityType.GROUP).size());
assertEquals(ROOT_GRP_CNT+2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "four");
pubAuthorityService.addAuthority(auth1, auth4);
+ authorityBridgeTableCache.forceInChangesForThisUncommittedTransaction();
assertEquals("GROUP_four", auth4);
assertEquals(GRP_CNT+4, getAllAuthorities(AuthorityType.GROUP).size());
assertEquals(ROOT_GRP_CNT+2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "five");
pubAuthorityService.addAuthority(auth2, auth5);
+ authorityBridgeTableCache.forceInChangesForThisUncommittedTransaction();
assertEquals("GROUP_five", auth5);
assertEquals(GRP_CNT+5, getAllAuthorities(AuthorityType.GROUP).size());
assertEquals(ROOT_GRP_CNT+2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
@@ -640,6 +645,7 @@ public class AuthorityServiceTest extends TestCase
//System.out.println("Users: "+ getAllAuthorities(AuthorityType.USER));
checkAuthorityCollectionSize(3, getAllAuthorities(AuthorityType.USER), AuthorityType.USER);
pubAuthorityService.addAuthority(auth5, "andy");
+ authorityBridgeTableCache.forceInChangesForThisUncommittedTransaction();
assertEquals(GRP_CNT+5, getAllAuthorities(AuthorityType.GROUP).size());
assertEquals(ROOT_GRP_CNT+2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
// The next call looks for people not users :-)
@@ -658,6 +664,7 @@ public class AuthorityServiceTest extends TestCase
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth5, false).contains("andy"));
pubAuthorityService.removeAuthority(auth5, "andy");
+ authorityBridgeTableCache.forceInChangesForThisUncommittedTransaction();
assertEquals(GRP_CNT+5, getAllAuthorities(AuthorityType.GROUP).size());
assertEquals(ROOT_GRP_CNT+2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
// The next call looks for people not users :-)
@@ -700,12 +707,14 @@ public class AuthorityServiceTest extends TestCase
assertEquals(ROOT_GRP_CNT+2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "five");
pubAuthorityService.addAuthority(auth2, auth5);
+ authorityBridgeTableCache.forceInChangesForThisUncommittedTransaction();
assertEquals(GRP_CNT+5, getAllAuthorities(AuthorityType.GROUP).size());
assertEquals(ROOT_GRP_CNT+2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
assertEquals(3, getAllAuthorities(AuthorityType.USER).size());
pubAuthorityService.addAuthority(auth5, "andy");
pubAuthorityService.addAuthority(auth1, "andy");
+ authorityBridgeTableCache.forceInChangesForThisUncommittedTransaction();
assertEquals(GRP_CNT+5, getAllAuthorities(AuthorityType.GROUP).size());
assertEquals(ROOT_GRP_CNT+2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
@@ -725,6 +734,7 @@ public class AuthorityServiceTest extends TestCase
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains("andy"));
pubAuthorityService.removeAuthority(auth1, "andy");
+ authorityBridgeTableCache.forceInChangesForThisUncommittedTransaction();
assertEquals(GRP_CNT+5, getAllAuthorities(AuthorityType.GROUP).size());
assertEquals(ROOT_GRP_CNT+2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
@@ -776,6 +786,7 @@ public class AuthorityServiceTest extends TestCase
checkAuthorityCollectionSize(3, getAllAuthorities(AuthorityType.USER), AuthorityType.USER);
pubAuthorityService.addAuthority(auth5, "andy");
pubAuthorityService.addAuthority(auth1, "andy");
+ authorityBridgeTableCache.forceInChangesForThisUncommittedTransaction();
assertEquals(GRP_CNT+5, getAllAuthorities(AuthorityType.GROUP).size());
assertEquals(ROOT_GRP_CNT+2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
@@ -795,6 +806,7 @@ public class AuthorityServiceTest extends TestCase
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains("andy"));
pubAuthorityService.addAuthority(auth3, auth2);
+ authorityBridgeTableCache.forceInChangesForThisUncommittedTransaction();
assertEquals(GRP_CNT+5, getAllAuthorities(AuthorityType.GROUP).size());
@@ -910,6 +922,7 @@ public class AuthorityServiceTest extends TestCase
pubAuthorityService.addAuthority(auth6, "andy1");
pubAuthorityService.addAuthority(auth6, "andy5");
pubAuthorityService.addAuthority(auth6, "andy6");
+ authorityBridgeTableCache.forceInChangesForThisUncommittedTransaction();
assertEquals(2, pubAuthorityService.getContainedAuthorities(null, auth1, true).size());
assertEquals(11, pubAuthorityService.getContainedAuthorities(null, auth1, false).size());
@@ -1079,6 +1092,8 @@ public class AuthorityServiceTest extends TestCase
String auth6 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "six");
pubAuthorityService.addAuthority(auth6, "an3dy");
+ authorityBridgeTableCache.forceInChangesForThisUncommittedTransaction();
+
assertEquals(1, pubAuthorityService.getContainedAuthorities(null, auth1, true).size());
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, true).contains("1234"));
assertEquals(1, pubAuthorityService.getContainedAuthorities(null, auth2, true).size());