package org.alien4cloud.tosca.editor; import java.io.IOException; import java.nio.file.Path; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; import javax.inject.Inject; import org.alien4cloud.tosca.catalog.events.BeforeArchiveDeleted; import org.alien4cloud.tosca.catalog.events.BeforeArchiveIndexed; import org.alien4cloud.tosca.catalog.events.BeforeArchivePromoted; import org.alien4cloud.tosca.catalog.index.CsarService; import org.alien4cloud.tosca.editor.operations.AbstractEditorOperation; import org.alien4cloud.tosca.editor.operations.UpdateFileOperation; import org.alien4cloud.tosca.model.Csar; import org.alien4cloud.tosca.model.templates.Topology; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.cache.RemovalListener; import com.google.common.cache.RemovalNotification; import alien4cloud.component.repository.IFileRepository; import alien4cloud.topology.TopologyServiceCore; import alien4cloud.tosca.context.ToscaContext; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; /** * The topology edition context manager is responsible to manage the caching and lifecycle of TopologyEditionContexts. */ @Slf4j @Component public class EditionContextManager { /** Holds the topology context */ private final static ThreadLocal<EditionContext> contextThreadLocal = new ThreadLocal<>(); @Inject private CsarService csarService; @Inject private TopologyServiceCore topologyServiceCore; @Inject private EditorRepositoryService repositoryService; @Inject private IFileRepository artifactRepository; // TODO make cache management time a parameter private LoadingCache<String, EditionContext> contextCache; @PostConstruct public void setup() { // initialize the cache contextCache = CacheBuilder.newBuilder().expireAfterAccess(30, TimeUnit.MINUTES).removalListener(new RemovalListener<String, EditionContext>() { @Override public void onRemoval(RemovalNotification<String, EditionContext> removalNotification) { log.debug("Topology edition context with id {} has been evicted. {} pending operations are lost.", removalNotification.getKey(), removalNotification.getValue().getOperations().size()); for (AbstractEditorOperation operation : removalNotification.getValue().getOperations()) { if (operation instanceof UpdateFileOperation) { String fileId = ((UpdateFileOperation) operation).getTempFileId(); if (artifactRepository.isFileExist(fileId)) { artifactRepository.deleteFile(fileId); } } } } }).build(new CacheLoader<String, EditionContext>() { @Override public EditionContext load(String csarId) throws Exception { log.debug("Loading edition context for archive {}", csarId); Csar csar = csarService.getOrFail(csarId); Topology topology = topologyServiceCore.getOrFail(csarId); // check if the topology git repository has been created already Path topologyGitPath = repositoryService.createGitDirectory(csar); log.debug("Edition context for archive {} loaded", csar); return new EditionContext(csar, topology, topologyGitPath); } }); } /** * Initialize thread local contexts for the topology. * * @param topologyId The id of the topology. */ @SneakyThrows public synchronized void init(String topologyId) { contextThreadLocal.set(contextCache.get(topologyId)); ToscaContext.set(contextThreadLocal.get().getToscaContext()); } /** * Reset the state of the topology context to it's initial state. * * @throws IOException In case the parsing of the directory content fails. */ public void reset() throws IOException { Topology topology = topologyServiceCore.getOrFail(getTopology().getId()); contextThreadLocal.get().reset(topology); ToscaContext.set(contextThreadLocal.get().getToscaContext()); } /** * Get the current topology edition context for the thread. * * @return The thread's topology edition context. */ public static EditionContext get() { return contextThreadLocal.get(); } /** * Get the archive of the current topology under edition. * * @return The thread's archive of the topology under edition. */ public static Csar getCsar() { return contextThreadLocal.get().getCsar(); } /** * Get the current topology under edition. * * @return The thread's topology under edition. */ public static Topology getTopology() { return contextThreadLocal.get().getTopology(); } /** * Remove thread local contexts. */ public void destroy() { contextThreadLocal.remove(); ToscaContext.destroy(); } @EventListener public void handleArchiveRemoved(BeforeArchiveDeleted event) { contextCache.invalidate(event.getArchiveId()); } @EventListener public void handleArchiveUpdated(BeforeArchiveIndexed event) { contextCache.invalidate(event.getArchiveRoot().getArchive().getId()); } @EventListener public void handleArchivePromoted(BeforeArchivePromoted event) { contextCache.invalidate(event.getArchiveId()); } /** * Invalidate all cached objects */ public void clearCache() { contextCache.invalidateAll(); } }