package rocks.inspectit.server.instrumentation.classcache; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import rocks.inspectit.server.instrumentation.classcache.events.INodeChangeListener; import rocks.inspectit.server.instrumentation.classcache.events.NodeEvent; import rocks.inspectit.server.instrumentation.classcache.events.ReferenceEvent; /** * The <code>ClassCache</code> holds the server-side representation of class structures. Each class * cache comes with its modification service and lookup service. * * <b> Only one write at a time. </b><br /> * The <code>ClassCache</code> ensures that only one writer can be active at one given time. * * <b> Multiple queries. </b> <br /> * The lookup facility supports parallel reads. Also the returned model elements allow to be read by * multiple threads. * * <b> "Simulated" safety by hiding. </b> <br /> * The <code>ClassCache</code> uses package access methods in order to ensure that only the core * classes within the class cache can access modification methods that would leak live instances. We * know that this is not the safest approach but as the structure is only used internally, it seems * to us to be a good alternative. * * @author Stefan Siegl */ @Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Lazy public class ClassCache { /** * The modification service. */ @Autowired private ClassCacheModification modificationService; /** * The lookup service. */ @Autowired private ClassCacheLookup lookupService; /** * The instrumentation service. */ @Autowired private ClassCacheInstrumentation instrumentationService; /** * List of listeners that are informed about changes to the model elements of the class cache. * Most likely indexing structures register themselves here. */ private final List<INodeChangeListener> nodeChangeListeners = new ArrayList<>(); /** * Lock of the structure. */ private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); /** * Read lock of the structure. */ private final Lock readLock = rwl.readLock(); /** * Write lock of the structure. */ private final Lock writeLock = rwl.writeLock(); /** * Initializes the services to the class cache. It is absolutely necessary to call this method * before using the class cache. * * As the services need a reference to the class cache instance building of the services cannot * happen within the constructor as we could then leak the "this" reference prior to having the * class cache built up completely. */ @PostConstruct void init() { lookupService.init(this); instrumentationService.init(this); modificationService.init(this); } /** * Executes given {@link Callable} with the read lock of the class cache. Result of the * {@link Callable} operation will be return. If exception occurs during call exception will be * propagated. * <p> * Note that this is a synchronous operation. * * @param <T> * type of result * @param callable * {@link Callable} to call. * @return Result of {@link Callable} call. * @throws Exception * If {@link Exception} occurs during call method. */ public <T> T executeWithReadLock(Callable<T> callable) throws Exception { readLock.lock(); try { return callable.call(); } finally { readLock.unlock(); } } /** * Executes given {@link Callable} with the write lock of the class cache. Result of the * {@link Callable} operation will be return. If exception occurs during call exception will be * propagated. * <p> * Note that this is synchronous operation. * * @param <T> * type of result * @param callable * {@link Callable} to call. * @return Result of {@link Callable} call. * @throws Exception * If {@link Exception} occurs during call method. */ public <T> T executeWithWriteLock(Callable<T> callable) throws Exception { writeLock.lock(); try { return callable.call(); } finally { writeLock.unlock(); } } /** * Inform the registered listeners about changes to the class node structure. * * Note that this method is synchronized. As it is ensured that only one thread can actively * change the structure this synchronization does not really hinder us. But is provides an * additional level of safety as the indexers can assume that they are only called single * threaded for sure. * * @param e * the changes to the structure. */ void informNodeChange(NodeEvent e) { for (INodeChangeListener listener : nodeChangeListeners) { listener.informNodeChange(e); } } /** * Inform the registered listeners about changes to the class reference structure. * * Note that this method is synchronized. As it is ensured that only one thread can actively * change the structure this synchronization does not really hinder us. But is provides an * additional level of safety as the indexers can assume that they are only called single * threaded for sure. * * @param e * the changes to the structure. */ void informReferenceChange(ReferenceEvent e) { for (INodeChangeListener listener : nodeChangeListeners) { listener.informReferenceChange(e); } } /** * {@inheritDoc} */ public synchronized void registerNodeChangeListener(INodeChangeListener listener) { nodeChangeListeners.add(listener); } /** * Returns the modification service. The modification service provides a set of services that * allow to change the class cache in a safe way. * * @return the modification service */ public ClassCacheModification getModificationService() { return modificationService; } /** * Returns the lookup service, which allows to search for specific entries within the class * cache. * * @return the lookup service, which allows to search for specific entries within the class * cache. */ public ClassCacheLookup getLookupService() { return lookupService; } /** * Returns the instrumentation service, which allows to perform instrumentation operations * within the class cache. * * @return the instrumentation service, which allows to perform instrumentation operations * within the class cache. */ public ClassCacheInstrumentation getInstrumentationService() { return instrumentationService; } }