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());