package io.neba.core.resourcemodels.registration; import org.osgi.service.event.Event; import org.osgi.service.event.EventHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import static java.util.concurrent.Executors.newSingleThreadExecutor; import static java.util.concurrent.TimeUnit.SECONDS; import static org.apache.sling.api.SlingConstants.PROPERTY_PATH; /** * Listens for <em>resource property</em> changes potentially altering the resource type -> model relationships. Invalidates the * {@link ModelRegistry} accordingly. * <p> * <p> * In Sling, the sling type hierarchy of a resource is defined by multiple attributes and can * be either explicit or implicit. For instance, a resource <em>may</em> explicitly specify a sling:resourceSuperType <em>or</em> the super type * could be derived implicitly from the sling:resourceSuperType of the type the resource's sling:resourceType property is * referring to, the former taking precedence. In addition, resources may specify <em>mixin types</em>, which can be added and removed dynamically. * </p> * <p> * Consequently, cached type hierarchy state must be cleared when these attributes change. This is what this event handler is responsible for. * <p> * <p> * Only valid cases handled herein. For instance, if a resource points to a sling:resourceType or sling:resourceSuperType, and * that type or super type resource is removed at runtime, this handler does not invalidate the cache as this represents an invalid * content state and is thus considered a programming error. * </p> * * @author Olaf Otto */ @Service public class MappableTypeHierarchyChangeListener implements EventHandler { private final Logger logger = LoggerFactory.getLogger(getClass()); private final ExecutorService executorService = newSingleThreadExecutor(); private final BlockingQueue<Object> invalidationRequests = new ArrayBlockingQueue<>(1); private boolean isShutDown = false; @Autowired private ModelRegistry modelRegistry; @PostConstruct protected void activate() { executorService.execute(() -> { while (!isShutDown) { try { Object path = invalidationRequests.poll(5, SECONDS); if (path != null) { if (logger.isTraceEnabled()) { logger.trace("Invalidating the resource model registry lookup cache due to changes to {}.", path); } modelRegistry.clearLookupCaches(); } } catch (InterruptedException e) { if (!isShutDown) { logger.debug("The type hierarchy change listener got interrupted, but was not shut down.", e); } } } }); } @PreDestroy protected void deactivate() { this.isShutDown = true; this.executorService.shutdownNow(); } /** * A substantial number of events may reach this handler. * However, we only need to clear the cache once. * Thus, if there already is a pending invalidation, * further invalidating events are simply discarded. */ @Override public void handleEvent(Event event) { invalidationRequests.offer(event.getProperty(PROPERTY_PATH)); } }