![JAR search and dependency download from the Maven repository](/logo.png)
step.functions.packages.FunctionPackageManager Maven / Gradle / Ivy
package step.functions.packages;
import static step.core.accessors.AbstractOrganizableObject.NAME;
import java.io.Closeable;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.exense.commons.app.Configuration;
import step.attachments.FileResolver;
import step.core.AbstractContext;
import step.core.objectenricher.ObjectEnricher;
import step.core.objectenricher.ObjectHookRegistry;
import step.functions.Function;
import step.functions.manager.FunctionManager;
import step.functions.type.FunctionTypeException;
import step.plugins.functions.types.CompositeFunction;
import step.resources.Resource;
import step.resources.ResourceManager;
/**
* This class is responsible for the handling of {@link FunctionPackage}
*
* It is responsible for the import of {@link FunctionPackage}. The import of a
* {@link FunctionPackage} means:
*
* - the persistence of the {@link FunctionPackage} itself
* - the definition of the {@link Function} contained in the package
* - the registration of change watcher of the package file of the
* {@link FunctionPackage}
*
*
*/
public class FunctionPackageManager implements Closeable {
private static final Logger logger = LoggerFactory.getLogger(FunctionPackageManager.class);
private static final String WATCH_FOR_CHANGE = "plugins.FunctionPackagePlugin.watchForChange";
private static final String WATCHER_INTERVAL = "plugins.FunctionPackagePlugin.watchForChange.interval";
private final FunctionPackageAccessor functionPackageAccessor;
private final FunctionManager functionRepository;
private final ResourceManager resourceManager;
private final FileResolver fileResolver;
private FunctionPackageChangeWatcher changeWatcher;
private final ObjectHookRegistry objectHookRegistry;
private final List packageHandlers = new ArrayList<>();
private final Map> attributeResolvers = new ConcurrentHashMap<>();
public FunctionPackageManager(FunctionPackageAccessor functionPackageAccessor, FunctionManager functionRepository,
ResourceManager resourceManager, FileResolver fileResolver, Configuration configuration,
ObjectHookRegistry objectHookRegistry) {
super();
this.functionPackageAccessor = functionPackageAccessor;
this.functionRepository = functionRepository;
this.resourceManager = resourceManager;
this.fileResolver = fileResolver;
this.objectHookRegistry = objectHookRegistry;
if (configuration.getPropertyAsBoolean(WATCH_FOR_CHANGE, true)) {
int interval = configuration.getPropertyAsInteger(WATCHER_INTERVAL, 60000);
changeWatcher = new FunctionPackageChangeWatcher(functionPackageAccessor, this, interval);
}
}
public void start() {
if (changeWatcher != null) {
changeWatcher.registerWatchers();
}
}
/**
* Registers a {@link FunctionPackageHandler}
*
* @param packageHandler the instance of the {@link FunctionPackageHandler}
*/
public void registerFunctionPackageHandler(FunctionPackageHandler packageHandler) {
packageHandlers.add(packageHandler);
}
public void registerAttributeResolver(String key, java.util.function.Function value) {
attributeResolvers.put(key, value);
}
/**
* Get the list of {@link Function} contained in the provided package
*
* @param functionPackage the {@link FunctionPackage} containing functions
* @return the list of {@link Function} found in the {@link FunctionPackage}
* @throws Exception if any error occurs during loading
*/
public List getPackagePreview(FunctionPackage functionPackage) throws Exception {
checkPackageValidity(functionPackage);
// Build the Functions with the corresponding handler
FunctionPackageHandler handler = getPackageHandler(functionPackage);
List functions = handler.buildFunctions(functionPackage, true, null);
return functions;
}
/**
* Adds or updates a {@link FunctionPackage} This triggers the import or
* re-import of the {@link Function}s contained in the package
*
* @param newFunctionPackage the {@link FunctionPackage} to be loaded
* @return the updated {@link FunctionPackage}
* @throws Exception if any error occurs during loading
*/
public FunctionPackage addOrUpdateFunctionPackage(FunctionPackage newFunctionPackage)
throws Exception {
checkPackageValidity(newFunctionPackage);
FunctionPackage previousFunctionPackage = null;
if (newFunctionPackage.getId() != null) {
previousFunctionPackage = get(newFunctionPackage.getId());
cleanupObsoleteResource(previousFunctionPackage, newFunctionPackage);
}
return addOrUpdateFunctionPackage(previousFunctionPackage, newFunctionPackage);
}
/**
* Reloads a {@link FunctionPackage}. This triggers a re-import of the
* {@link Function}s contained in the package
*
* @param functionPackageId the ID of the {@link FunctionPackage} to be reloaded
* @return the updated {@link FunctionPackage}
* @throws Exception if any error occurs during reloading
*/
public FunctionPackage reloadFunctionPackage(String functionPackageId) throws Exception {
assert functionPackageId != null;
FunctionPackage functionPackage = getFunctionPackage(functionPackageId);
assert functionPackage != null;
return addOrUpdateFunctionPackage(functionPackage, functionPackage);
}
public FunctionPackage getFunctionPackage(String id) {
return get(new ObjectId(id));
}
public List getPackageFunctions(String functionPackageId) {
return getPackageFunctions(getFunctionPackage(functionPackageId));
}
public void removeFunctionPackage(String id) {
remove(new ObjectId(id));
}
@Override
public void close() throws IOException {
if (changeWatcher != null) {
changeWatcher.close();
}
}
private void checkPackageValidity(FunctionPackage functionPackage) throws Exception {
if (functionPackage.getPackageLocation()==null) {
throw new Exception("Invalid package location: resource was null");
} else if (functionPackage.getPackageLocation().isEmpty()) {
throw new Exception("Invalid package location: resource was empty");
}
}
private FunctionPackageHandler getPackageHandler(FunctionPackage functionPackage)
throws UnsupportedFunctionPackageType {
return packageHandlers.stream().filter(f -> f.isValidForPackage(functionPackage)).findFirst()
.orElseThrow(() -> new UnsupportedFunctionPackageType(
"Unsupported package type: " + functionPackage.getPackageLocation()));
}
private void cleanupObsoleteResource(FunctionPackage previousFunctionPackage, FunctionPackage newFunctionPackage) {
if (previousFunctionPackage != null && newFunctionPackage != null) {
// cleanup main resource
if (previousFunctionPackage.getPackageLocation() != null
&& newFunctionPackage.getPackageLocation() != null) {
String previousResourceId = getResourceId(previousFunctionPackage);
String newResourceId = getResourceId(newFunctionPackage);
if (previousResourceId != null && !previousResourceId.equals(newResourceId)) {
if (resourceManager.resourceExists(previousResourceId)) {
resourceManager.deleteResource(previousResourceId);
}
}
}
// cleanup library resource
// in SED-2341 we decided to remove cleanup of linked libraries,
// because normally these libraries can be shared between several keyword packages
// if (previousFunctionPackage.getPackageLibrariesLocation() != null
// && newFunctionPackage.getPackageLibrariesLocation() != null) {
// String previousResourceId = getLibraryResourceId(previousFunctionPackage);
// String newResourceId = getLibraryResourceId(newFunctionPackage);
// if (previousResourceId != null && !previousResourceId.equals(newResourceId)) {
// if (resourceManager.resourceExists(previousResourceId)) {
// resourceManager.deleteResource(previousResourceId);
// }
// }
// }
}
}
private List deleteFunctions(FunctionPackage previousFunctionPackage) {
List previousFunctions = getPackageFunctions(previousFunctionPackage);
previousFunctions.forEach(function -> {
try {
functionRepository.deleteFunction(function.getId().toString());
} catch (FunctionTypeException e) {
logger.error("Error while deleting function " + function.getId().toString(), e);
}
});
return previousFunctions;
}
private FunctionPackage addOrUpdateFunctionPackage(FunctionPackage previousFunctionPackage,
FunctionPackage newFunctionPackage) throws Exception {
checkPackageValidity(newFunctionPackage);
// Auto-detect the appropriate package handler
FunctionPackageHandler handler = getPackageHandler(newFunctionPackage);
// resolve the attribute values if necessary
if (newFunctionPackage.getAttributes() != null) {
newFunctionPackage.setAttributes(newFunctionPackage.getAttributes().entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> resolveAttribute(e.getKey(), e.getValue()))));
}
// apply context attributes of the function package to the function
AbstractContext context = new AbstractContext() {};
objectHookRegistry.rebuildContext(context, newFunctionPackage);
ObjectEnricher objectEnricher = objectHookRegistry.getObjectEnricher(context);
// Remove functions from previous package
List previousFunctions = null;
if (previousFunctionPackage != null) {
previousFunctions = deleteFunctions(previousFunctionPackage);
unregisterWatcher(previousFunctionPackage);
}
// Build the Functions with the appropriate handler
List functions = handler.buildFunctions(newFunctionPackage, false, objectEnricher);
List newFunctionIds = new ArrayList<>();
for (Function newFunction : functions) {
// apply packageAttributes
if (newFunctionPackage.getPackageAttributes() != null) {
newFunction.getAttributes().putAll(newFunctionPackage.getPackageAttributes());
}
objectEnricher.accept(newFunction);
// search for an existing function with the same name and reuse its ID
// this is needed as long Plans refer to Functions by ID
if (previousFunctions != null) {
previousFunctions.stream().filter(f -> f.getAttribute(NAME).equals(newFunction.getAttribute(NAME)))
.findFirst().ifPresent(oldFunction -> newFunction.setId(oldFunction.getId()));
}
newFunction.setManaged(true);
newFunction.setExecuteLocally(newFunctionPackage.isExecuteLocally() ||
(newFunction instanceof CompositeFunction));
newFunction.setTokenSelectionCriteria(newFunctionPackage.getTokenSelectionCriteria());
newFunction.addCustomField(FunctionPackageEntity.FUNCTION_PACKAGE_ID,
newFunctionPackage.getId().toString());
functionRepository.saveFunction(newFunction);
newFunctionIds.add(newFunction.getId());
}
// keep track of the created functions
newFunctionPackage.setFunctions(newFunctionIds);
// set the name of the function package
String name = buildFunctionPackageName(newFunctionPackage);
newFunctionPackage.addAttribute(NAME, name);
registerWatcher(newFunctionPackage);
// persist the changes
newFunctionPackage = functionPackageAccessor.save(newFunctionPackage);
return newFunctionPackage;
}
private String buildFunctionPackageName(FunctionPackage newFunctionPackage) {
String name;
String resourceId = getResourceId(newFunctionPackage);
if (resourceId != null) {
Resource mainResource = resourceManager.getResource(resourceId);
if (mainResource != null) {
name = mainResource.getResourceName();
} else {
throw new RuntimeException("The resource with id " + resourceId + " could not be found");
}
} else {
Path p = Paths.get(newFunctionPackage.getPackageLocation());
name = p.getFileName().toString();
}
return name;
}
private String resolveAttribute(String key, String value) {
// get the attribute resolver for the specified attribute
java.util.function.Function attributeResolver = attributeResolvers.get(key);
if (attributeResolver != null) {
// Is it a value to be resolved i.e starting with @?
if (value != null && value.startsWith("@")) {
String valueToBeResolved = value.replaceFirst("@", "");
String resolvedValue = attributeResolver.apply(valueToBeResolved);
return resolvedValue;
} else {
return value;
}
} else {
return value;
}
}
private void registerWatcher(FunctionPackage functionPackage) {
if (changeWatcher != null && !fileResolver.isResource(functionPackage.getPackageLocation())) {
functionPackage.setWatchForChange(true);
changeWatcher.registerWatcherForPackage(functionPackage);
}
}
private void unregisterWatcher(FunctionPackage functionPackage) {
if (changeWatcher != null && !fileResolver.isResource(functionPackage.getPackageLocation())) {
changeWatcher.unregisterWatcher(functionPackage);
}
}
private String getLibraryResourceId(FunctionPackage fpackage) {
return resolveResourceId(fpackage.getPackageLibrariesLocation());
}
private String getResourceId(FunctionPackage fpackage) {
return resolveResourceId(fpackage.getPackageLocation());
}
private String resolveResourceId(String resourceLocation) {
return fileResolver.resolveResourceId(resourceLocation);
}
private List getPackageFunctions(FunctionPackage functionPackage) {
return functionPackage.functions.stream().map(id -> functionRepository.getFunctionById(id.toString()))
.filter(Objects::nonNull).collect(Collectors.toList());
}
private FunctionPackage get(ObjectId id) {
return functionPackageAccessor.get(id);
}
private void remove(ObjectId id) {
FunctionPackage functionPackage = functionPackageAccessor.get(id);
deleteFunctions(functionPackage);
unregisterWatcher(functionPackage);
functionPackageAccessor.remove(id);
deleteResource(functionPackage.getPackageLocation());
deleteResource(functionPackage.getPackageLibrariesLocation());
}
private void deleteResource(String path) {
String resolveResourceId = fileResolver.resolveResourceId(path);
// Is it a resource?
if (resolveResourceId != null) {
// if yes, delete it
try {
resourceManager.deleteResource(resolveResourceId);
} catch (RuntimeException e) {
logger.warn(
"Dirty cleanup of FunctionPackage: an error occured while deleting one of the associated resources.",
e);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy