package org.opennms.netmgt.provision.persist; import java.net.URL; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import org.opennms.netmgt.provision.persist.foreignsource.ForeignSource; import org.opennms.netmgt.provision.persist.requisition.Requisition; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.Resource; import org.springframework.util.Assert; public class CachingForeignSourceRepository extends AbstractForeignSourceRepository implements InitializingBean { private final ReentrantReadWriteLock m_globalLock = new ReentrantReadWriteLock(true); private final ReadLock m_readLock = m_globalLock.readLock(); private final WriteLock m_writeLock = m_globalLock.writeLock(); private ForeignSourceRepository m_foreignSourceRepository; private long m_refreshInterval; private Set<String> m_dirtyForeignSources = new HashSet<String>(); private Set<String> m_dirtyRequisitions = new HashSet<String>(); private Set<String> m_foreignSourceNames; private Map<String,ForeignSource> m_foreignSources; private Map<String,Requisition> m_requisitions; private ForeignSource m_defaultForeignSource; private ScheduledExecutorService m_executor; public CachingForeignSourceRepository() { m_refreshInterval = Long.getLong("org.opennms.netmgt.provision.persist.cacheRefreshInterval", 300000); final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); executor.setContinueExistingPeriodicTasksAfterShutdownPolicy(false); executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); executor.setMaximumPoolSize(1); m_executor = executor; // every refreshInterval milliseconds, save any modifications, and clean out existing cached data m_executor.scheduleAtFixedRate(getRefreshRunnable(), m_refreshInterval, m_refreshInterval, TimeUnit.MILLISECONDS); } protected void writeUnlock() { if (m_globalLock.getWriteHoldCount() > 0) { m_writeLock.unlock(); } } protected void writeLock() { if (m_globalLock.getWriteHoldCount() == 0) { while (m_globalLock.getReadHoldCount() > 0) { m_readLock.unlock(); } m_writeLock.lock(); } } protected void readUnlock() { if (m_globalLock.getReadHoldCount() > 0) { m_readLock.unlock(); } } protected void readLock() { m_readLock.lock(); } protected void cleanCache() { getRefreshRunnable().run(); } protected Runnable getRefreshRunnable() { return new Runnable() { @Override public void run() { writeLock(); try { // clear foreign source name cache m_foreignSourceNames = null; // clear the foreign source cache if (m_dirtyForeignSources.size() > 0) { for (final String dirtyForeignSource : m_dirtyForeignSources) { final ForeignSource fs = m_foreignSources.get(dirtyForeignSource); if (fs == null) { m_foreignSourceRepository.delete(fs); } else { m_foreignSourceRepository.save(fs); } } m_dirtyForeignSources.clear(); } m_foreignSources = null; // clear the requisition cache if (m_dirtyRequisitions.size() > 0) { for (final String dirtyRequisition : m_dirtyRequisitions) { final Requisition r = m_requisitions.get(dirtyRequisition); if (r == null) { m_foreignSourceRepository.delete(r); } else { m_foreignSourceRepository.save(r); } } m_dirtyForeignSources.clear(); } m_requisitions = null; } finally { writeUnlock(); } } }; } public ForeignSourceRepository getForeignSourceRepository() { return m_foreignSourceRepository; } public void setForeignSourceRepository(final ForeignSourceRepository fsr) { m_foreignSourceRepository = fsr; } public void afterPropertiesSet() { Assert.notNull(m_foreignSourceRepository); } @Override public Set<String> getActiveForeignSourceNames() { readLock(); try { if (m_foreignSourceNames == null) { m_foreignSourceNames = m_foreignSourceRepository.getActiveForeignSourceNames(); } return m_foreignSourceNames; } finally { readUnlock(); } } @Override public int getForeignSourceCount() throws ForeignSourceRepositoryException { readLock(); try { return getForeignSources().size(); } finally { readUnlock(); } } private Map<String,ForeignSource> getForeignSourceMap() { readLock(); try { if (m_foreignSources == null) { writeLock(); try { final Map<String,ForeignSource> fses = new TreeMap<String,ForeignSource>(); for (final ForeignSource fs : m_foreignSourceRepository.getForeignSources()) { fses.put(fs.getName(), fs); } m_foreignSources = fses; } finally { readLock(); writeUnlock(); } } return m_foreignSources; } finally { readUnlock(); } } @Override public Set<ForeignSource> getForeignSources() throws ForeignSourceRepositoryException { readLock(); try { return new TreeSet<ForeignSource>(getForeignSourceMap().values()); } finally { readUnlock(); } } @Override public ForeignSource getForeignSource(final String foreignSourceName) throws ForeignSourceRepositoryException { readLock(); try { ForeignSource fs = getForeignSourceMap().get(foreignSourceName); if (fs == null) { fs = getDefaultForeignSource(); fs.setName(foreignSourceName); } return fs; } finally { readUnlock(); } } @Override public void save(final ForeignSource foreignSource) throws ForeignSourceRepositoryException { readLock(); try { final Map<String,ForeignSource> fses = getForeignSourceMap(); fses.put(foreignSource.getName(), foreignSource); m_dirtyForeignSources.add(foreignSource.getName()); } finally { readUnlock(); } } @Override public void delete(final ForeignSource foreignSource) throws ForeignSourceRepositoryException { readLock(); try { final Map<String,ForeignSource> fses = getForeignSourceMap(); fses.remove(foreignSource.getName()); m_dirtyForeignSources.add(foreignSource.getName()); } finally { readUnlock(); } } @Override public ForeignSource getDefaultForeignSource() throws ForeignSourceRepositoryException { readLock(); try { if (m_defaultForeignSource == null) { writeLock(); try { m_defaultForeignSource = m_foreignSourceRepository.getDefaultForeignSource(); } finally { readLock(); readUnlock(); } } return m_defaultForeignSource; } finally { readUnlock(); } } @Override public void putDefaultForeignSource(final ForeignSource foreignSource) throws ForeignSourceRepositoryException { writeLock(); try { cleanCache(); m_foreignSourceRepository.putDefaultForeignSource(foreignSource); } finally { writeUnlock(); } } @Override public void resetDefaultForeignSource() throws ForeignSourceRepositoryException { writeLock(); try { cleanCache(); m_foreignSourceRepository.resetDefaultForeignSource(); } finally { writeUnlock(); } } private Map<String,Requisition> getRequisitionMap() { readLock(); try { if (m_requisitions == null) { writeLock(); try { final Map<String,Requisition> requisitions = new TreeMap<String,Requisition>(); for (final Requisition requisition : m_foreignSourceRepository.getRequisitions()) { requisitions.put(requisition.getForeignSource(), requisition); } m_requisitions = requisitions; } finally { readLock(); writeUnlock(); } } return m_requisitions; } finally { readUnlock(); } } @Override public Requisition importResourceRequisition(final Resource resource) throws ForeignSourceRepositoryException { readLock(); try { return super.importResourceRequisition(resource); } finally { readUnlock(); } } @Override public Set<Requisition> getRequisitions() throws ForeignSourceRepositoryException { readLock(); try { return new TreeSet<Requisition>(getRequisitionMap().values()); } finally { readUnlock(); } } @Override public Requisition getRequisition(final String foreignSourceName) throws ForeignSourceRepositoryException { readLock(); try { return getRequisitionMap().get(foreignSourceName); } finally { readUnlock(); } } @Override public Requisition getRequisition(final ForeignSource foreignSource) throws ForeignSourceRepositoryException { readLock(); try { return getRequisitionMap().get(foreignSource.getName()); } finally { readUnlock(); } } @Override public URL getRequisitionURL(final String foreignSource) { return m_foreignSourceRepository.getRequisitionURL(foreignSource); } @Override public void save(final Requisition requisition) throws ForeignSourceRepositoryException { writeLock(); try { getRequisitionMap().put(requisition.getForeignSource(), requisition); m_dirtyRequisitions.add(requisition.getForeignSource()); } finally { writeUnlock(); } } @Override public void delete(final Requisition requisition) throws ForeignSourceRepositoryException { writeLock(); try { getRequisitionMap().remove(requisition.getForeignSource()); m_dirtyRequisitions.add(requisition.getForeignSource()); } finally { writeUnlock(); } } @Override public OnmsNodeRequisition getNodeRequisition(final String foreignSource, final String foreignId) throws ForeignSourceRepositoryException { readLock(); try { final Requisition requisition = getRequisitionMap().get(foreignSource); return (requisition == null? null : requisition.getNodeRequistion(foreignId)); } finally { readUnlock(); } } @Override public void validate(final ForeignSource foreignSource) throws ForeignSourceRepositoryException { super.validate(foreignSource); } @Override public void validate(final Requisition requisition) throws ForeignSourceRepositoryException { super.validate(requisition); } @Override protected void finalize() throws Throwable { m_executor.shutdown(); cleanCache(); super.finalize(); } }