package alien4cloud.tosca.context;
import alien4cloud.component.ICSARRepositorySearchService;
import alien4cloud.tosca.model.ArchiveRoot;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.alien4cloud.tosca.model.CSARDependency;
import org.alien4cloud.tosca.model.Csar;
import org.alien4cloud.tosca.model.types.*;
import java.util.*;
import java.util.function.Predicate;
import static alien4cloud.utils.AlienUtils.safe;
/**
* Manage thread-local tosca contexts.
*/
@Slf4j
public class ToscaContext {
@Setter
private static ICSARRepositorySearchService csarRepositorySearchService;
private final static ThreadLocal<Context> contextThreadLocal = new ThreadLocal<>();
/**
* Create a new instance of Context.
*
* @param dependencies The list of dependencies for this context.
* @return An instance of a Context.
*/
public static void init(final Set<CSARDependency> dependencies) {
contextThreadLocal.set(new Context(dependencies));
}
/**
* Set an existing tosca context.
*
* @param context The context to set.
*/
public static void set(Context context) {
contextThreadLocal.set(context);
}
/**
* Get the context for the current thread.
*
* @return The context.
*/
public static Context get() {
return contextThreadLocal.get();
}
/**
* Get an element from the local-cache or from ES.
*
* @param elementClass The class of the element to look for.
* @param elementId The id of the element to look for.
* @param <T> The type of element.
* @return The requested element.
*/
public static <T extends AbstractToscaType> T get(Class<T> elementClass, String elementId) {
return contextThreadLocal.get().getElement(elementClass, elementId, false);
}
/**
* Get an element from the local-cache or from ES.
*
* @param elementClass The class of the element to look for.
* @param elementId The id of the element to look for.
* @param <T> The type of element.
* @return The requested element.
*/
public static <T extends AbstractToscaType> T getOrFail(Class<T> elementClass, String elementId) {
return contextThreadLocal.get().getElement(elementClass, elementId, true);
}
/**
* Destroy the tosca context.
*/
public static void destroy() {
contextThreadLocal.remove();
}
/**
* Tosca context allows to cache TOSCA elements
*/
public static class Context {
@Getter
/** Current context dependencies. */
private final Set<CSARDependency> dependencies;
/** Context archives. */
private final Map<String, Csar> archivesMap = Maps.newHashMap();
/** Cached types in the context. <ElementType, <ElementId, Element>> */
private final Map<String, Map<String, AbstractToscaType>> toscaTypesCache = Maps.newHashMap();
public Context(Set<CSARDependency> dependencies) {
this.dependencies = dependencies;
}
private CSARDependency getDependencyByName(String dependencyName) {
for (CSARDependency d : dependencies) {
if (d.getName().equals(dependencyName)) {
return d;
}
}
return null;
}
/**
* Update the ToscaContext to take in account the new dependencies.
*
* @param newDependencies The new list of dependencies for this context.
*/
public void resetDependencies(Set<CSARDependency> newDependencies) {
Map<String, CSARDependency> dependenciesByName = Maps.newHashMap();
for (CSARDependency dependency : dependencies) {
dependenciesByName.put(dependency.getName(), dependency);
}
// now add/ update /remove dependencies to match the new dependencies.
for (CSARDependency dependency : newDependencies) {
CSARDependency previous = dependenciesByName.remove(dependency.getName());
if (previous == null) {
addDependency(dependency);
} else if (!previous.getVersion().equals(dependency.getVersion())) {
updateDependency(dependency);
}
}
for (CSARDependency dependency : dependenciesByName.values()) {
removeDependency(dependency);
}
}
/**
* Add a dependency to the current context.
*
* @param dependency The dependency to add.
*/
public void addDependency(CSARDependency dependency) {
log.debug("Add dependency to context", dependency);
if (dependency.getHash() == null) {
// we should try to get the hash from the repository
Csar csar = getArchive(dependency.getName(), dependency.getVersion());
dependency.setHash(csar.getHash());
}
dependencies.add(dependency);
}
/**
* Remove a given dependency from the TOSCA Context by name.
*
* @param removedDependency The dependency to remove.
*/
public void removeDependency(CSARDependency removedDependency) {
CSARDependency existingDependency = getDependencyByName(removedDependency.getName());
if (existingDependency != null) {
dependencies.remove(existingDependency);
List<String> toRemove = Lists.newArrayList();
// unload all types from this dependency
for (Map<String, AbstractToscaType> elementMap : toscaTypesCache.values()) {
for (Map.Entry<String, AbstractToscaType> entry : elementMap.entrySet()) {
if (entry.getValue().getArchiveName().equals(existingDependency.getName())
&& entry.getValue().getArchiveVersion().equals(existingDependency.getVersion())) {
toRemove.add(entry.getKey());
}
}
for (String removeId : toRemove) {
elementMap.remove(removeId);
}
toRemove.clear();
}
// alse clean the archive cache
Csar csar = new Csar(removedDependency.getName(), removedDependency.getVersion());
archivesMap.remove(csar.getId());
log.debug("Removed dependency {} from the TOSCA context.", removedDependency);
} else {
log.debug("Cannot remove dependency {} from the TOSCA context as it wasn't found in the dependencies.", removedDependency);
}
}
/**
* Performs an add or update of a dependency in a TOSCA context.
*
* @param dependency The updated dependency.
*/
public void updateDependency(CSARDependency dependency) {
// Do not update dependency if the version hasn't changed
if (hasDependency(dependency)) {
log.debug("Dependency already exist in context.");
return;
}
removeDependency(dependency);
addDependency(dependency);
}
private boolean hasDependency(CSARDependency dependency) {
for (CSARDependency existDependency : this.dependencies) {
if (existDependency.getName().equals(dependency.getName())) {
return existDependency.getVersion().equals(dependency.getVersion()) && Objects.equals(existDependency.getHash(), dependency.getHash());
}
}
return false;
}
/**
* Load all elements from the given archive in the context.
*
* @param root The parsed archive to load.
*/
public void register(ArchiveRoot root) {
log.debug("Register archive {}", root);
archivesMap.put(root.getArchive().getId(), root.getArchive());
register(ArtifactType.class, root.getArtifactTypes());
register(CapabilityType.class, root.getCapabilityTypes());
register(DataType.class, root.getDataTypes());
register(NodeType.class, root.getNodeTypes());
register(RelationshipType.class, root.getRelationshipTypes());
}
private <T extends AbstractToscaType> void register(Class<T> elementClass, Map<String, T> elementMap) {
String elementType = elementClass.getSimpleName();
Map<String, AbstractToscaType> typeElements = toscaTypesCache.get(elementType);
if (typeElements == null) {
typeElements = new HashMap<>();
toscaTypesCache.put(elementType, typeElements);
}
if (elementMap == null) {
return;
}
typeElements.putAll(elementMap);
}
/**
* Get an archive from it's id.
*
* @param name The name of the archive to get.
* @param version The version of the archive to get.
* @return The archive from it's id.
*/
public Csar getArchive(String name, String version) {
String id = new Csar(name, version).getId();
Csar archive = archivesMap.get(id);
log.debug("get archive from map {} {} {}", id, archive);
if (archive == null) {
archive = csarRepositorySearchService.getArchive(name, version);
log.debug("get archive from repo {} {} {}", id, archive, csarRepositorySearchService.getClass().getName());
archivesMap.put(id, archive);
}
return archive;
}
/**
* Get an element from the local-cache or from ES.
*
* @param elementClass The class of the element to look for.
* @param elementId The id of the element to look for.
* @param <T> The type of element.
* @return The requested element.
*/
public <T extends AbstractToscaType> T getElement(Class<T> elementClass, String elementId, boolean required) {
String elementType = elementClass.getSimpleName();
Map<String, AbstractToscaType> typeElements = toscaTypesCache.get(elementType);
if (typeElements == null) {
typeElements = new HashMap<>();
toscaTypesCache.put(elementType, typeElements);
} else {
// find in local-cache
T element = (T) typeElements.get(elementId);
if (element != null) {
return element;
}
}
T element = required ? csarRepositorySearchService.getRequiredElementInDependencies(elementClass, elementId, dependencies)
: csarRepositorySearchService.getElementInDependencies(elementClass, elementId, dependencies);
if (element != null) {
typeElements.put(elementId, element);
}
log.debug("Retrieve element {} {}", element, dependencies);
return element;
}
public <T extends AbstractToscaType> Optional<AbstractToscaType> getElement(Class<T> elementClass, Predicate<AbstractToscaType> filter) {
String elementType = elementClass.getSimpleName();
Map<String, AbstractToscaType> typeElements = toscaTypesCache.get(elementType);
return safe(typeElements).values().stream().filter(filter).findFirst();
}
}
}