From 45144cda1f38e3ab94d502516f0db7c314b0e3f0 Mon Sep 17 00:00:00 2001 From: Andreas Haufler Date: Tue, 2 Apr 2024 10:43:45 +0200 Subject: [PATCH 1/7] Provides a generic framework to handle custom events on a. "per tenant" base --- .../sirius/biz/scripting/CustomEvent.java | 52 ++++++++ .../biz/scripting/CustomEventDispatcher.java | 33 ++++++ .../CustomEventDispatcherRepository.java | 44 +++++++ .../biz/scripting/CustomEventRegistry.java | 42 +++++++ .../sirius/biz/scripting/CustomEvents.java | 93 +++++++++++++++ .../biz/scripting/EntityCustomEvent.java | 40 +++++++ .../ScriptBasedCustomEventDispatcher.java | 111 ++++++++++++++++++ .../biz/scripting/TypedCustomEvent.java | 26 ++++ src/main/resources/biz_de.properties | 2 + 9 files changed, 443 insertions(+) create mode 100644 src/main/java/sirius/biz/scripting/CustomEvent.java create mode 100644 src/main/java/sirius/biz/scripting/CustomEventDispatcher.java create mode 100644 src/main/java/sirius/biz/scripting/CustomEventDispatcherRepository.java create mode 100644 src/main/java/sirius/biz/scripting/CustomEventRegistry.java create mode 100644 src/main/java/sirius/biz/scripting/CustomEvents.java create mode 100644 src/main/java/sirius/biz/scripting/EntityCustomEvent.java create mode 100644 src/main/java/sirius/biz/scripting/ScriptBasedCustomEventDispatcher.java create mode 100644 src/main/java/sirius/biz/scripting/TypedCustomEvent.java diff --git a/src/main/java/sirius/biz/scripting/CustomEvent.java b/src/main/java/sirius/biz/scripting/CustomEvent.java new file mode 100644 index 000000000..0e19e0154 --- /dev/null +++ b/src/main/java/sirius/biz/scripting/CustomEvent.java @@ -0,0 +1,52 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.biz.scripting; + +import sirius.kernel.health.HandledException; + +import java.util.Optional; + +/** + * Provides a base class for all custom events handled by a {@link CustomEventDispatcher}. + */ +public abstract class CustomEvent { + + protected boolean success; + protected boolean failed; + protected HandledException error; + + /** + * Determines if the event was successful. + * + * @return true if the event was successful, false otherwise + */ + public boolean isSuccess() { + return success; + } + + /** + * Determines if the event failed (an exception occurred within an event handler). + *

+ * Note, that the exception itself can be obtained via {@link #getError()}. + * + * @return true if the event failed, false otherwise + */ + public boolean isFailed() { + return failed; + } + + /** + * Provides the error which occurred when handling the event. + * + * @return the error that occurred wrapped as optional or an empty optional if the event was successful + */ + public Optional getError() { + return Optional.ofNullable(error); + } +} diff --git a/src/main/java/sirius/biz/scripting/CustomEventDispatcher.java b/src/main/java/sirius/biz/scripting/CustomEventDispatcher.java new file mode 100644 index 000000000..08d23b66c --- /dev/null +++ b/src/main/java/sirius/biz/scripting/CustomEventDispatcher.java @@ -0,0 +1,33 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.biz.scripting; + +/** + * Describes a dispatcher which can handle custom events. + *

+ * This is usually fetched via {@link CustomEvents} and will handle all events for a given tenant, + * based on a given script. If no custom handling script is present, a NOOP dispatcher is used + * which will be marked as {@link #isActive() inactive} (so that some events might get optimized away). + */ +public interface CustomEventDispatcher { + + /** + * Determines if this dispatcher is active and will actually handle events. + * + * @return true if a real dispatcher is present, false if a NOOP dispatcher is used + */ + boolean isActive(); + + /** + * Handles the given event. + * + * @param event the event to handle + */ + void handleEvent(CustomEvent event); +} diff --git a/src/main/java/sirius/biz/scripting/CustomEventDispatcherRepository.java b/src/main/java/sirius/biz/scripting/CustomEventDispatcherRepository.java new file mode 100644 index 000000000..ecf928bc3 --- /dev/null +++ b/src/main/java/sirius/biz/scripting/CustomEventDispatcherRepository.java @@ -0,0 +1,44 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.biz.scripting; + +import sirius.kernel.di.std.AutoRegister; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Optional; + +/** + * Provides a repository which stores and manages {@link CustomEventDispatcher custom event dispatchers}. + *

+ * Note that the repository isn't usually accessed directly. Instead, the {@link CustomEvents} class should be used. + */ +@AutoRegister +public interface CustomEventDispatcherRepository { + + /** + * Fetches all available dispatchers for the given tenant. + * + * @param tenantId the tenant for which to fetch the dispatchers + * @return a list of all available dispatchers for the given tenant + */ + List fetchAvailableDispatchers(@Nonnull String tenantId); + + /** + * Fetches the dispatcher with the given name for the given tenant. + * + * @param tenantId the tenant for which to fetch the dispatcher + * @param name the name of the dispatcher to fetch + * @return the dispatcher with the given name for the given tenant wrapped as optional or an empty optional if + * no such dispatcher exists. NOTE: if an empty name is given, the first dispatcher for the given tenant + * is used. This helps to simplify the usage of custom events. + */ + Optional fetchDispatcher(@Nonnull String tenantId, @Nullable String name); +} diff --git a/src/main/java/sirius/biz/scripting/CustomEventRegistry.java b/src/main/java/sirius/biz/scripting/CustomEventRegistry.java new file mode 100644 index 000000000..5e17939db --- /dev/null +++ b/src/main/java/sirius/biz/scripting/CustomEventRegistry.java @@ -0,0 +1,42 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.biz.scripting; + +import sirius.kernel.commons.Callback; + +/** + * Provides the interface as seen by the scripting engine to register custom event handlers. + *

+ * The tenant specific script is provided with an instance of this registry and can then add handlers + * as needed. + */ +public interface CustomEventRegistry { + + /** + * Adds a handler for the given event type. + * + * @param eventType the type of events to handle + * @param handler the handler to handle the event + * @param the generic type of the event + */ + void registerHandler(Class eventType, Callback handler); + + /** + * Adds a typed handler for the given event type and inner type + * + * @param eventType the type of events to handle + * @param type the inner type within the event to process + * @param handler the handler to handle the event + * @param the generic inner type of the event + * @param the generic type of the event + */ + > void registerTypedHandler(Class eventType, + Class type, + Callback handler); +} diff --git a/src/main/java/sirius/biz/scripting/CustomEvents.java b/src/main/java/sirius/biz/scripting/CustomEvents.java new file mode 100644 index 000000000..ad29c2e98 --- /dev/null +++ b/src/main/java/sirius/biz/scripting/CustomEvents.java @@ -0,0 +1,93 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.biz.scripting; + +import sirius.kernel.di.std.Part; +import sirius.kernel.di.std.Register; +import sirius.web.security.ScopeInfo; +import sirius.web.security.UserContext; +import sirius.web.security.UserInfo; + +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.List; + +/** + * Provides access to tenant specific custom event dispatchers. + *

+ * Event dispatchers are stored and managed by a {@link CustomEventDispatcherRepository}. Commonly these are + * defined via scripts which modify a {@link CustomEventRegistry} and are then transformed into a + * {@link CustomEventDispatcher} with the help of a {@link ScriptBasedCustomEventDispatcher}. + */ +@Register(classes = CustomEvents.class) +public class CustomEvents { + + private static final CustomEventDispatcher NOOP_DISPATCHER = new CustomEventDispatcher() { + + @Override + public boolean isActive() { + return false; + } + + @Override + public void handleEvent(CustomEvent event) { + // do nothing + } + }; + + @Part + @Nullable + private CustomEventDispatcherRepository dispatcherRepository; + + /** + * Fetches the dispatcher for the current tenant. + * + * @param name the name of the dispatcher to fetch + * @return the dispatcher for the current tenant with the given name or a NOOP dispatcher if no such dispatcher + * exists. Note, if an empty name is given, the first available dispatcher for the current tenant is used. + * This way, if exactly one dispatcher is present, it will be used in all import processes etc. + */ + public CustomEventDispatcher fetchDispatcherForCurrentTenant(@Nullable String name) { + if (dispatcherRepository == null) { + return NOOP_DISPATCHER; + } + + if (!ScopeInfo.DEFAULT_SCOPE.getScopeType().equals(UserContext.getCurrentScope().getScopeType())) { + return NOOP_DISPATCHER; + } + + UserInfo currentUser = UserContext.getCurrentUser(); + if (!currentUser.isLoggedIn()) { + return NOOP_DISPATCHER; + } + + return dispatcherRepository.fetchDispatcher(currentUser.getTenantId(), name).orElse(NOOP_DISPATCHER); + } + + /** + * Fetches all available dispatchers for the current tenant. + * + * @return a list of all available dispatchers for the current tenant + */ + public List fetchDispatchersForCurrentTenant() { + if (dispatcherRepository == null) { + return Collections.emptyList(); + } + if (!ScopeInfo.DEFAULT_SCOPE.getScopeType().equals(UserContext.getCurrentScope().getScopeType())) { + return Collections.emptyList(); + } + + UserInfo currentUser = UserContext.getCurrentUser(); + if (!currentUser.isLoggedIn()) { + return Collections.emptyList(); + } + + return dispatcherRepository.fetchAvailableDispatchers(currentUser.getTenantId()); + } +} diff --git a/src/main/java/sirius/biz/scripting/EntityCustomEvent.java b/src/main/java/sirius/biz/scripting/EntityCustomEvent.java new file mode 100644 index 000000000..068a8582e --- /dev/null +++ b/src/main/java/sirius/biz/scripting/EntityCustomEvent.java @@ -0,0 +1,40 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.biz.scripting; + +import sirius.db.mixing.Entity; + +/** + * Provides a base class for all custom events which are associated with an entity. + * + * @param the generic type of the entity + */ +public abstract class EntityCustomEvent extends TypedCustomEvent { + + private final E entity; + + protected EntityCustomEvent(E entity) { + this.entity = entity; + } + + /** + * Returns the entity associated with this event. + * + * @return the entity associated with this event + */ + public E getEntity() { + return entity; + } + + @SuppressWarnings("unchecked") + @Override + public Class getType() { + return (Class) entity.getClass(); + } +} diff --git a/src/main/java/sirius/biz/scripting/ScriptBasedCustomEventDispatcher.java b/src/main/java/sirius/biz/scripting/ScriptBasedCustomEventDispatcher.java new file mode 100644 index 000000000..4d82ac436 --- /dev/null +++ b/src/main/java/sirius/biz/scripting/ScriptBasedCustomEventDispatcher.java @@ -0,0 +1,111 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.biz.scripting; + +import sirius.biz.process.Processes; +import sirius.kernel.async.TaskContext; +import sirius.kernel.commons.Callback; +import sirius.kernel.commons.Watch; +import sirius.kernel.di.std.Part; +import sirius.kernel.health.Exceptions; +import sirius.kernel.health.HandledException; +import sirius.kernel.nls.NLS; + +import java.util.HashMap; +import java.util.Map; + +/** + * Provides a {@link CustomEventDispatcher} which also implements {@link CustomEventRegistry}. + *

+ * An instance of this class can be first supplied to a custom script in order to pick up handlers and will then + * be used to dispatch upcoming events to these handlers. + */ +public class ScriptBasedCustomEventDispatcher implements CustomEventDispatcher, CustomEventRegistry { + + @Part + private static Processes processes; + + private volatile boolean active = false; + + private final Map> handlers = new HashMap<>(); + + @Override + public void registerHandler(Class eventType, Callback handler) { + handlers.put(eventType.getName(), handler); + active = true; + } + + @Override + public > void registerTypedHandler(Class eventType, + Class type, + Callback handler) { + handlers.put(buildTypedEventHandlerName(eventType, type), handler); + active = true; + } + + private String buildTypedEventHandlerName(Class eventType, Class type) { + return eventType.getName() + "::" + type.getName(); + } + + @Override + public boolean isActive() { + return active; + } + + @SuppressWarnings("unchecked") + @Override + public void handleEvent(CustomEvent event) { + Callback handler = (Callback) handlers.get(determineEventKey(event)); + if (handler == null) { + return; + } + + Watch watch = Watch.start(); + try { + handler.invoke(event); + event.success = true; + } catch (HandledException handledException) { + handleEventHandlerException(handledException); + event.failed = true; + event.error = handledException; + } catch (Exception e) { + HandledException handledException = Exceptions.handle(Scripting.LOG, e); + handleEventHandlerException(handledException); + event.failed = true; + event.error = handledException; + } + TaskContext.get().addTiming(NLS.get("CustomEventHandler.customEvents"), watch.elapsedMillis()); + } + + private String determineEventKey(CustomEvent event) { + if (event instanceof TypedCustomEvent typedEvent) { + return buildTypedEventHandlerName(typedEvent.getClass(), typedEvent.getType()); + } else { + return event.getClass().getName(); + } + } + + private void handleEventHandlerException(HandledException handledException) { + if (processes.fetchCurrentProcess().isPresent()) { + handleExceptionInProcess(handledException); + } else { + processes.executeInStandbyProcessForCurrentTenant("custom-event-handler", + () -> NLS.get("CustomEventHandler.customEvents"), + ignored -> handleExceptionInProcess(handledException)); + } + } + + private void handleExceptionInProcess(HandledException handledException) { + TaskContext.get() + .log(NLS.fmtr("CustomEventHandler.message") + .set("system", TaskContext.get().getSystemString()) + .set("message", handledException.getMessage()) + .format()); + } +} diff --git a/src/main/java/sirius/biz/scripting/TypedCustomEvent.java b/src/main/java/sirius/biz/scripting/TypedCustomEvent.java new file mode 100644 index 000000000..8efafce37 --- /dev/null +++ b/src/main/java/sirius/biz/scripting/TypedCustomEvent.java @@ -0,0 +1,26 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.biz.scripting; + +/** + * Provides a base class for all custom events which can be internally typed to a specified type. + *

+ * This might be used to events which are the same for many classed (e.g. update events for entities). + * + * @param the type of object for which this event occurred. + */ +public abstract class TypedCustomEvent extends CustomEvent { + + /** + * The of objects for which this event occurred. + * + * @return the type of object for which this event occurred + */ + public abstract Class getType(); +} diff --git a/src/main/resources/biz_de.properties b/src/main/resources/biz_de.properties index 46cb916f3..9172973d4 100644 --- a/src/main/resources/biz_de.properties +++ b/src/main/resources/biz_de.properties @@ -196,6 +196,8 @@ Country.us = USA Country.vn = Vietnam Country.xk = Kosovo Country.za = Südafrika +CustomEventHandler.customEvents = Kunden-Scripting +CustomEventHandler.message = Fehler in Kunden-Script - System: ${system}: ${message} DashboardBrowserDistributionChart.description = Zeigt die Anzahl der Aufrufe der Backend-Startseite je Browser an. DashboardBrowserDistributionChart.label = Browser-Verteilung im Backend DashboardController.help = Hilfe & Support From c284064b67e4bd11f2b37a7fe285067148fa3498 Mon Sep 17 00:00:00 2001 From: Andreas Haufler Date: Tue, 2 Apr 2024 10:54:44 +0200 Subject: [PATCH 2/7] Provides a MongoDB specific custom script repository --- .../MongoCustomEventDispatcherRepository.java | 107 +++++++++++++++++ .../scripting/mongo/MongoCustomScript.java | 69 +++++++++++ .../mongo/MongoCustomScriptController.java | 110 ++++++++++++++++++ src/main/resources/biz_de.properties | 4 + .../biz/scripting/mongo-script.html.pasta | 74 ++++++++++++ .../biz/scripting/mongo-scripts.html.pasta | 46 ++++++++ 6 files changed, 410 insertions(+) create mode 100644 src/main/java/sirius/biz/scripting/mongo/MongoCustomEventDispatcherRepository.java create mode 100644 src/main/java/sirius/biz/scripting/mongo/MongoCustomScript.java create mode 100644 src/main/java/sirius/biz/scripting/mongo/MongoCustomScriptController.java create mode 100644 src/main/resources/default/templates/biz/scripting/mongo-script.html.pasta create mode 100644 src/main/resources/default/templates/biz/scripting/mongo-scripts.html.pasta diff --git a/src/main/java/sirius/biz/scripting/mongo/MongoCustomEventDispatcherRepository.java b/src/main/java/sirius/biz/scripting/mongo/MongoCustomEventDispatcherRepository.java new file mode 100644 index 000000000..5be5458c6 --- /dev/null +++ b/src/main/java/sirius/biz/scripting/mongo/MongoCustomEventDispatcherRepository.java @@ -0,0 +1,107 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.biz.scripting.mongo; + +import sirius.biz.scripting.CustomEventDispatcher; +import sirius.biz.scripting.CustomEventDispatcherRepository; +import sirius.biz.scripting.CustomEventRegistry; +import sirius.biz.scripting.ScriptBasedCustomEventDispatcher; +import sirius.db.mongo.Mango; +import sirius.kernel.async.TaskContext; +import sirius.kernel.commons.Strings; +import sirius.kernel.di.std.Part; +import sirius.kernel.di.std.Register; +import sirius.kernel.health.HandledException; +import sirius.kernel.tokenizer.Position; +import sirius.pasta.noodle.Callable; +import sirius.pasta.noodle.ScriptingException; +import sirius.pasta.noodle.SimpleEnvironment; +import sirius.pasta.noodle.compiler.CompilationContext; +import sirius.pasta.noodle.compiler.NoodleCompiler; +import sirius.pasta.noodle.compiler.SourceCodeInfo; +import sirius.pasta.noodle.sandbox.SandboxMode; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Optional; + +/** + * Stores and manages {@link CustomEventDispatcher custom event dispatchers} in a MongoDB. + */ +@Register(framework = MongoCustomEventDispatcherRepository.FRAMEWORK_SCRIPTING_MONGO) +public class MongoCustomEventDispatcherRepository implements CustomEventDispatcherRepository { + + protected static final String FRAMEWORK_SCRIPTING_MONGO = "biz.scripting-mongo"; + + /** + * Contains the name of the variable which holds the {@link CustomEventRegistry} in a script. + */ + public static final String SCRIPT_PARAMETER_REGISTRY = "registry"; + + @Part + private Mango mango; + + @Override + public List fetchAvailableDispatchers(@Nonnull String tenantId) { + return mango.select(MongoCustomScript.class) + .eq(MongoCustomScript.TENANT, tenantId) + .orderAsc(MongoCustomScript.CODE) + .queryList() + .stream() + .map(MongoCustomScript::getCode) + .toList(); + } + + @Override + public Optional fetchDispatcher(@Nonnull String tenantId, @Nullable String name) { + if (Strings.isEmpty(name)) { + List mongoCustomScripts = + mango.select(MongoCustomScript.class).eq(MongoCustomScript.TENANT, tenantId).limit(2).queryList(); + if (mongoCustomScripts.size() == 1) { + return compileAndLoad(mongoCustomScripts.getFirst()); + } else { + return Optional.empty(); + } + } else { + return mango.select(MongoCustomScript.class) + .eq(MongoCustomScript.TENANT, tenantId) + .eq(MongoCustomScript.CODE, name) + .first() + .flatMap(this::compileAndLoad); + } + } + + private Optional compileAndLoad(MongoCustomScript script) { + try { + if (Strings.isEmpty(script.getScript())) { + return Optional.empty(); + } + + CompilationContext compilationContext = + new CompilationContext(SourceCodeInfo.forInlineCode(script.getScript(), SandboxMode.WARN_ONLY)); + + compilationContext.getVariableScoper() + .defineVariable(Position.UNKNOWN, SCRIPT_PARAMETER_REGISTRY, CustomEventRegistry.class); + NoodleCompiler compiler = new NoodleCompiler(compilationContext); + Callable compiledScript = compiler.compileScript(); + + ScriptBasedCustomEventDispatcher dispatcher = new ScriptBasedCustomEventDispatcher(); + SimpleEnvironment environment = new SimpleEnvironment(); + environment.writeVariable(0, dispatcher); + compiledScript.call(environment); + + return Optional.of(dispatcher); + } catch (ScriptingException | HandledException e) { + TaskContext.get() + .log("Failed compiling custom event dispatcher '%s': %s", script.getCode(), e.getMessage()); + return Optional.empty(); + } + } +} diff --git a/src/main/java/sirius/biz/scripting/mongo/MongoCustomScript.java b/src/main/java/sirius/biz/scripting/mongo/MongoCustomScript.java new file mode 100644 index 000000000..4b2276b09 --- /dev/null +++ b/src/main/java/sirius/biz/scripting/mongo/MongoCustomScript.java @@ -0,0 +1,69 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.biz.scripting.mongo; + +import sirius.biz.importer.AutoImport; +import sirius.biz.mongo.PrefixSearchContent; +import sirius.biz.tenants.mongo.MongoTenantAware; +import sirius.biz.web.Autoloaded; +import sirius.db.mixing.Mapping; +import sirius.db.mixing.annotations.NullAllowed; +import sirius.db.mixing.annotations.Unique; +import sirius.kernel.commons.Strings; +import sirius.kernel.nls.NLS; + +/** + * Stores a custom scripting profile for a tenant. + */ +public class MongoCustomScript extends MongoTenantAware { + + /** + * Contains the code or name of the script. + */ + public static final Mapping CODE = Mapping.named("code"); + @Unique(within = "tenant") + @PrefixSearchContent + @Autoloaded + @AutoImport + private String code; + + /** + * Contains the actual scripting code. + */ + public static final Mapping SCRIPT = Mapping.named("script"); + @NullAllowed + @Autoloaded + @AutoImport + private String script; + + @Override + public String toString() { + if (Strings.isFilled(code)) { + return code; + } else { + return NLS.get("MongoCustomScript.unnamedScript"); + } + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getScript() { + return script; + } + + public void setScript(String script) { + this.script = script; + } +} diff --git a/src/main/java/sirius/biz/scripting/mongo/MongoCustomScriptController.java b/src/main/java/sirius/biz/scripting/mongo/MongoCustomScriptController.java new file mode 100644 index 000000000..73ba642d2 --- /dev/null +++ b/src/main/java/sirius/biz/scripting/mongo/MongoCustomScriptController.java @@ -0,0 +1,110 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.biz.scripting.mongo; + +import sirius.biz.scripting.CustomEventRegistry; +import sirius.biz.scripting.ScriptingController; +import sirius.biz.web.BizController; +import sirius.biz.web.MongoPageHelper; +import sirius.db.mixing.query.QueryField; +import sirius.db.mongo.Mango; +import sirius.kernel.di.std.Part; +import sirius.kernel.di.std.Register; +import sirius.kernel.tokenizer.Position; +import sirius.pasta.noodle.compiler.CompilationContext; +import sirius.pasta.noodle.compiler.NoodleCompiler; +import sirius.pasta.noodle.compiler.SourceCodeInfo; +import sirius.pasta.noodle.sandbox.SandboxMode; +import sirius.pasta.tagliatelle.compiler.TemplateCompiler; +import sirius.web.controller.Routed; +import sirius.web.http.WebContext; +import sirius.web.security.Permission; +import sirius.web.services.InternalService; +import sirius.web.services.JSONStructuredOutput; + +/** + * Provides the management UI for {@link MongoCustomScript custom scripts}. + */ +@Register(framework = MongoCustomEventDispatcherRepository.FRAMEWORK_SCRIPTING_MONGO) +public class MongoCustomScriptController extends BizController { + + private static final String PARAM_SCRIPT = "script"; + + @Part + private Mango mango; + + /** + * Lists all scripts available for the current tenant. + * + * @param webContext the request to handle + */ + @Routed("/scripting/scripts") + @Permission(ScriptingController.PERMISSION_SCRIPTING) + public void listScripts(WebContext webContext) { + MongoPageHelper pageHelper = + MongoPageHelper.withQuery(tenants.forCurrentTenant(mango.select(MongoCustomScript.class) + .orderAsc(MongoCustomScript.CODE))) + .withContext(webContext); + + pageHelper.withSearchFields(QueryField.startsWith(MongoCustomScript.SEARCH_PREFIXES)); + webContext.respondWith().template("/templates/biz/scripting/mongo-scripts.html.pasta", pageHelper.asPage()); + } + + /** + * Modifies / manages the given script. + * + * @param webContext the request to handle + */ + @Routed("/scripting/scripts/:1") + @Permission(ScriptingController.PERMISSION_SCRIPTING) + public void editScript(WebContext webContext, String id) { + MongoCustomScript script = findForTenant(MongoCustomScript.class, id); + boolean requestHandled = prepareSave(webContext).withAfterSaveURI("/scripting/scripts").saveEntity(script); + if (!requestHandled) { + webContext.respondWith().template("/templates/biz/scripting/mongo-script.html.pasta", script); + } + } + + /** + * Deletes the given script. + * + * @param webContext the request to handle + */ + @Routed("/scripting/scripts/:1/delete") + @Permission(ScriptingController.PERMISSION_SCRIPTING) + public void deleteScript(WebContext webContext, String id) { + deleteEntity(webContext, tryFindForTenant(MongoCustomScript.class, id)); + webContext.respondWith().redirectToGet("/scripting/scripts"); + } + + /** + * Runs the compiler on a given script and reports all errors or warnings. + * + * @param webContext the request to handle + * @param output the output to write to + */ + @Routed("/scripting/api/compile") + @InternalService + @Permission(ScriptingController.PERMISSION_SCRIPTING) + public void compile(WebContext webContext, JSONStructuredOutput output) { + if (webContext.isSafePOST()) { + String script = webContext.get(PARAM_SCRIPT).asString(); + CompilationContext compilationContext = + new CompilationContext(SourceCodeInfo.forInlineCode(script, SandboxMode.WARN_ONLY)); + compilationContext.getVariableScoper() + .defineVariable(Position.UNKNOWN, + MongoCustomEventDispatcherRepository.SCRIPT_PARAMETER_REGISTRY, + CustomEventRegistry.class); + + NoodleCompiler compiler = new NoodleCompiler(compilationContext); + compiler.compileScript(); + TemplateCompiler.reportAsJson(compilationContext.getErrors(), output); + } + } +} diff --git a/src/main/resources/biz_de.properties b/src/main/resources/biz_de.properties index 9172973d4..cb398392a 100644 --- a/src/main/resources/biz_de.properties +++ b/src/main/resources/biz_de.properties @@ -668,6 +668,10 @@ MongoCodeListExportJobFactory.description = Exportiert eine Codeliste als CSV od MongoCodeListExportJobFactory.label = Codeliste exportieren MongoCodeListImportJobFactory.description = Importiert eine Codeliste aus einer CSV oder Excel Datei. MongoCodeListImportJobFactory.label = Codeliste importieren +MongoCustomScript.code = Code +MongoCustomScript.plural = Kunden-Scripte +MongoCustomScript.script = Quelltext +MongoCustomScript.unnamedScript = Unbenanntes Script MongoTenantExportJobFactory.description = Exportiert Mandanten in eine CSV oder Excel Datei. MongoTenantExportJobFactory.label = Mandanten exportieren MongoUserAccountExportJobFactory.description = Exportiert Anwender in eine CSV oder Excel Datei. diff --git a/src/main/resources/default/templates/biz/scripting/mongo-script.html.pasta b/src/main/resources/default/templates/biz/scripting/mongo-script.html.pasta new file mode 100644 index 000000000..dc1dc29eb --- /dev/null +++ b/src/main/resources/default/templates/biz/scripting/mongo-script.html.pasta @@ -0,0 +1,74 @@ + + + + + + + + +

  • + @i18n("MongoCustomScript.plural") +
  • +
  • + @script +
  • + + + + + + + + + + + + +
    + @script.getScript() +
    + + +
    + + + diff --git a/src/main/resources/default/templates/biz/scripting/mongo-scripts.html.pasta b/src/main/resources/default/templates/biz/scripting/mongo-scripts.html.pasta new file mode 100644 index 000000000..da8d1ec1b --- /dev/null +++ b/src/main/resources/default/templates/biz/scripting/mongo-scripts.html.pasta @@ -0,0 +1,46 @@ + + + + +
  • + @i18n("MongoCustomScript.plural") +
  • +
    + + + + + + + + +
    +
    + + + + + + + + + + + + + + + +
    @i18n("MongoCustomScript.code") +
    + @script.getCode() + + +
    + +
    +
    +
    +
    From b92853f7ab004c20c1c0c12ae4fda7d1be7758dc Mon Sep 17 00:00:00 2001 From: Andreas Haufler Date: Tue, 2 Apr 2024 22:34:00 +0200 Subject: [PATCH 3/7] Renames the framework and its classes to properly reveal its purpose --- ...mEvent.java => EntityScriptableEvent.java} | 4 ++-- ...{CustomEvent.java => ScriptableEvent.java} | 4 ++-- ...er.java => ScriptableEventDispatcher.java} | 6 ++--- ... ScriptableEventDispatcherRepository.java} | 8 +++---- ...stry.java => ScriptableEventRegistry.java} | 10 ++++----- ...ustomEvents.java => ScriptableEvents.java} | 18 +++++++-------- ...a => SimpleScriptableEventDispatcher.java} | 22 +++++++++---------- ...omEvent.java => TypedScriptableEvent.java} | 2 +- .../MongoCustomEventDispatcherRepository.java | 22 +++++++++---------- .../mongo/MongoCustomScriptController.java | 4 ++-- 10 files changed, 50 insertions(+), 50 deletions(-) rename src/main/java/sirius/biz/scripting/{EntityCustomEvent.java => EntityScriptableEvent.java} (83%) rename src/main/java/sirius/biz/scripting/{CustomEvent.java => ScriptableEvent.java} (94%) rename src/main/java/sirius/biz/scripting/{CustomEventDispatcher.java => ScriptableEventDispatcher.java} (80%) rename src/main/java/sirius/biz/scripting/{CustomEventDispatcherRepository.java => ScriptableEventDispatcherRepository.java} (79%) rename src/main/java/sirius/biz/scripting/{CustomEventRegistry.java => ScriptableEventRegistry.java} (81%) rename src/main/java/sirius/biz/scripting/{CustomEvents.java => ScriptableEvents.java} (77%) rename src/main/java/sirius/biz/scripting/{ScriptBasedCustomEventDispatcher.java => SimpleScriptableEventDispatcher.java} (79%) rename src/main/java/sirius/biz/scripting/{TypedCustomEvent.java => TypedScriptableEvent.java} (90%) diff --git a/src/main/java/sirius/biz/scripting/EntityCustomEvent.java b/src/main/java/sirius/biz/scripting/EntityScriptableEvent.java similarity index 83% rename from src/main/java/sirius/biz/scripting/EntityCustomEvent.java rename to src/main/java/sirius/biz/scripting/EntityScriptableEvent.java index 068a8582e..caeaa3f96 100644 --- a/src/main/java/sirius/biz/scripting/EntityCustomEvent.java +++ b/src/main/java/sirius/biz/scripting/EntityScriptableEvent.java @@ -15,11 +15,11 @@ * * @param the generic type of the entity */ -public abstract class EntityCustomEvent extends TypedCustomEvent { +public abstract class EntityScriptableEvent extends TypedScriptableEvent { private final E entity; - protected EntityCustomEvent(E entity) { + protected EntityScriptableEvent(E entity) { this.entity = entity; } diff --git a/src/main/java/sirius/biz/scripting/CustomEvent.java b/src/main/java/sirius/biz/scripting/ScriptableEvent.java similarity index 94% rename from src/main/java/sirius/biz/scripting/CustomEvent.java rename to src/main/java/sirius/biz/scripting/ScriptableEvent.java index 0e19e0154..ed84065cf 100644 --- a/src/main/java/sirius/biz/scripting/CustomEvent.java +++ b/src/main/java/sirius/biz/scripting/ScriptableEvent.java @@ -13,9 +13,9 @@ import java.util.Optional; /** - * Provides a base class for all custom events handled by a {@link CustomEventDispatcher}. + * Provides a base class for all custom events handled by a {@link ScriptableEventDispatcher}. */ -public abstract class CustomEvent { +public abstract class ScriptableEvent { protected boolean success; protected boolean failed; diff --git a/src/main/java/sirius/biz/scripting/CustomEventDispatcher.java b/src/main/java/sirius/biz/scripting/ScriptableEventDispatcher.java similarity index 80% rename from src/main/java/sirius/biz/scripting/CustomEventDispatcher.java rename to src/main/java/sirius/biz/scripting/ScriptableEventDispatcher.java index 08d23b66c..77ac29ca4 100644 --- a/src/main/java/sirius/biz/scripting/CustomEventDispatcher.java +++ b/src/main/java/sirius/biz/scripting/ScriptableEventDispatcher.java @@ -11,11 +11,11 @@ /** * Describes a dispatcher which can handle custom events. *

    - * This is usually fetched via {@link CustomEvents} and will handle all events for a given tenant, + * This is usually fetched via {@link ScriptableEvents} and will handle all events for a given tenant, * based on a given script. If no custom handling script is present, a NOOP dispatcher is used * which will be marked as {@link #isActive() inactive} (so that some events might get optimized away). */ -public interface CustomEventDispatcher { +public interface ScriptableEventDispatcher { /** * Determines if this dispatcher is active and will actually handle events. @@ -29,5 +29,5 @@ public interface CustomEventDispatcher { * * @param event the event to handle */ - void handleEvent(CustomEvent event); + void handleEvent(ScriptableEvent event); } diff --git a/src/main/java/sirius/biz/scripting/CustomEventDispatcherRepository.java b/src/main/java/sirius/biz/scripting/ScriptableEventDispatcherRepository.java similarity index 79% rename from src/main/java/sirius/biz/scripting/CustomEventDispatcherRepository.java rename to src/main/java/sirius/biz/scripting/ScriptableEventDispatcherRepository.java index ecf928bc3..351a4d904 100644 --- a/src/main/java/sirius/biz/scripting/CustomEventDispatcherRepository.java +++ b/src/main/java/sirius/biz/scripting/ScriptableEventDispatcherRepository.java @@ -16,12 +16,12 @@ import java.util.Optional; /** - * Provides a repository which stores and manages {@link CustomEventDispatcher custom event dispatchers}. + * Provides a repository which stores and manages {@link ScriptableEventDispatcher custom event dispatchers}. *

    - * Note that the repository isn't usually accessed directly. Instead, the {@link CustomEvents} class should be used. + * Note that the repository isn't usually accessed directly. Instead, the {@link ScriptableEvents} class should be used. */ @AutoRegister -public interface CustomEventDispatcherRepository { +public interface ScriptableEventDispatcherRepository { /** * Fetches all available dispatchers for the given tenant. @@ -40,5 +40,5 @@ public interface CustomEventDispatcherRepository { * no such dispatcher exists. NOTE: if an empty name is given, the first dispatcher for the given tenant * is used. This helps to simplify the usage of custom events. */ - Optional fetchDispatcher(@Nonnull String tenantId, @Nullable String name); + Optional fetchDispatcher(@Nonnull String tenantId, @Nullable String name); } diff --git a/src/main/java/sirius/biz/scripting/CustomEventRegistry.java b/src/main/java/sirius/biz/scripting/ScriptableEventRegistry.java similarity index 81% rename from src/main/java/sirius/biz/scripting/CustomEventRegistry.java rename to src/main/java/sirius/biz/scripting/ScriptableEventRegistry.java index 5e17939db..4df73b9b3 100644 --- a/src/main/java/sirius/biz/scripting/CustomEventRegistry.java +++ b/src/main/java/sirius/biz/scripting/ScriptableEventRegistry.java @@ -16,7 +16,7 @@ * The tenant specific script is provided with an instance of this registry and can then add handlers * as needed. */ -public interface CustomEventRegistry { +public interface ScriptableEventRegistry { /** * Adds a handler for the given event type. @@ -25,7 +25,7 @@ public interface CustomEventRegistry { * @param handler the handler to handle the event * @param the generic type of the event */ - void registerHandler(Class eventType, Callback handler); + void registerHandler(Class eventType, Callback handler); /** * Adds a typed handler for the given event type and inner type @@ -36,7 +36,7 @@ public interface CustomEventRegistry { * @param the generic inner type of the event * @param the generic type of the event */ - > void registerTypedHandler(Class eventType, - Class type, - Callback handler); + > void registerTypedHandler(Class eventType, + Class type, + Callback handler); } diff --git a/src/main/java/sirius/biz/scripting/CustomEvents.java b/src/main/java/sirius/biz/scripting/ScriptableEvents.java similarity index 77% rename from src/main/java/sirius/biz/scripting/CustomEvents.java rename to src/main/java/sirius/biz/scripting/ScriptableEvents.java index ad29c2e98..ec274152b 100644 --- a/src/main/java/sirius/biz/scripting/CustomEvents.java +++ b/src/main/java/sirius/biz/scripting/ScriptableEvents.java @@ -21,14 +21,14 @@ /** * Provides access to tenant specific custom event dispatchers. *

    - * Event dispatchers are stored and managed by a {@link CustomEventDispatcherRepository}. Commonly these are - * defined via scripts which modify a {@link CustomEventRegistry} and are then transformed into a - * {@link CustomEventDispatcher} with the help of a {@link ScriptBasedCustomEventDispatcher}. + * Event dispatchers are stored and managed by a {@link ScriptableEventDispatcherRepository}. Commonly these are + * defined via scripts which modify a {@link ScriptableEventRegistry} and are then transformed into a + * {@link ScriptableEventDispatcher} with the help of a {@link SimpleScriptableEventDispatcher}. */ -@Register(classes = CustomEvents.class) -public class CustomEvents { +@Register(classes = ScriptableEvents.class) +public class ScriptableEvents { - private static final CustomEventDispatcher NOOP_DISPATCHER = new CustomEventDispatcher() { + private static final ScriptableEventDispatcher NOOP_DISPATCHER = new ScriptableEventDispatcher() { @Override public boolean isActive() { @@ -36,14 +36,14 @@ public boolean isActive() { } @Override - public void handleEvent(CustomEvent event) { + public void handleEvent(ScriptableEvent event) { // do nothing } }; @Part @Nullable - private CustomEventDispatcherRepository dispatcherRepository; + private ScriptableEventDispatcherRepository dispatcherRepository; /** * Fetches the dispatcher for the current tenant. @@ -53,7 +53,7 @@ public void handleEvent(CustomEvent event) { * exists. Note, if an empty name is given, the first available dispatcher for the current tenant is used. * This way, if exactly one dispatcher is present, it will be used in all import processes etc. */ - public CustomEventDispatcher fetchDispatcherForCurrentTenant(@Nullable String name) { + public ScriptableEventDispatcher fetchDispatcherForCurrentTenant(@Nullable String name) { if (dispatcherRepository == null) { return NOOP_DISPATCHER; } diff --git a/src/main/java/sirius/biz/scripting/ScriptBasedCustomEventDispatcher.java b/src/main/java/sirius/biz/scripting/SimpleScriptableEventDispatcher.java similarity index 79% rename from src/main/java/sirius/biz/scripting/ScriptBasedCustomEventDispatcher.java rename to src/main/java/sirius/biz/scripting/SimpleScriptableEventDispatcher.java index 4d82ac436..83d980af4 100644 --- a/src/main/java/sirius/biz/scripting/ScriptBasedCustomEventDispatcher.java +++ b/src/main/java/sirius/biz/scripting/SimpleScriptableEventDispatcher.java @@ -21,30 +21,30 @@ import java.util.Map; /** - * Provides a {@link CustomEventDispatcher} which also implements {@link CustomEventRegistry}. + * Provides a {@link ScriptableEventDispatcher} which also implements {@link ScriptableEventRegistry}. *

    * An instance of this class can be first supplied to a custom script in order to pick up handlers and will then * be used to dispatch upcoming events to these handlers. */ -public class ScriptBasedCustomEventDispatcher implements CustomEventDispatcher, CustomEventRegistry { +public class SimpleScriptableEventDispatcher implements ScriptableEventDispatcher, ScriptableEventRegistry { @Part private static Processes processes; private volatile boolean active = false; - private final Map> handlers = new HashMap<>(); + private final Map> handlers = new HashMap<>(); @Override - public void registerHandler(Class eventType, Callback handler) { + public void registerHandler(Class eventType, Callback handler) { handlers.put(eventType.getName(), handler); active = true; } @Override - public > void registerTypedHandler(Class eventType, - Class type, - Callback handler) { + public > void registerTypedHandler(Class eventType, + Class type, + Callback handler) { handlers.put(buildTypedEventHandlerName(eventType, type), handler); active = true; } @@ -60,8 +60,8 @@ public boolean isActive() { @SuppressWarnings("unchecked") @Override - public void handleEvent(CustomEvent event) { - Callback handler = (Callback) handlers.get(determineEventKey(event)); + public void handleEvent(ScriptableEvent event) { + Callback handler = (Callback) handlers.get(determineEventKey(event)); if (handler == null) { return; } @@ -83,8 +83,8 @@ public void handleEvent(CustomEvent event) { TaskContext.get().addTiming(NLS.get("CustomEventHandler.customEvents"), watch.elapsedMillis()); } - private String determineEventKey(CustomEvent event) { - if (event instanceof TypedCustomEvent typedEvent) { + private String determineEventKey(ScriptableEvent event) { + if (event instanceof TypedScriptableEvent typedEvent) { return buildTypedEventHandlerName(typedEvent.getClass(), typedEvent.getType()); } else { return event.getClass().getName(); diff --git a/src/main/java/sirius/biz/scripting/TypedCustomEvent.java b/src/main/java/sirius/biz/scripting/TypedScriptableEvent.java similarity index 90% rename from src/main/java/sirius/biz/scripting/TypedCustomEvent.java rename to src/main/java/sirius/biz/scripting/TypedScriptableEvent.java index 8efafce37..021e94061 100644 --- a/src/main/java/sirius/biz/scripting/TypedCustomEvent.java +++ b/src/main/java/sirius/biz/scripting/TypedScriptableEvent.java @@ -15,7 +15,7 @@ * * @param the type of object for which this event occurred. */ -public abstract class TypedCustomEvent extends CustomEvent { +public abstract class TypedScriptableEvent extends ScriptableEvent { /** * The of objects for which this event occurred. diff --git a/src/main/java/sirius/biz/scripting/mongo/MongoCustomEventDispatcherRepository.java b/src/main/java/sirius/biz/scripting/mongo/MongoCustomEventDispatcherRepository.java index 5be5458c6..fac5cfa28 100644 --- a/src/main/java/sirius/biz/scripting/mongo/MongoCustomEventDispatcherRepository.java +++ b/src/main/java/sirius/biz/scripting/mongo/MongoCustomEventDispatcherRepository.java @@ -8,10 +8,10 @@ package sirius.biz.scripting.mongo; -import sirius.biz.scripting.CustomEventDispatcher; -import sirius.biz.scripting.CustomEventDispatcherRepository; -import sirius.biz.scripting.CustomEventRegistry; -import sirius.biz.scripting.ScriptBasedCustomEventDispatcher; +import sirius.biz.scripting.ScriptableEventDispatcher; +import sirius.biz.scripting.ScriptableEventDispatcherRepository; +import sirius.biz.scripting.ScriptableEventRegistry; +import sirius.biz.scripting.SimpleScriptableEventDispatcher; import sirius.db.mongo.Mango; import sirius.kernel.async.TaskContext; import sirius.kernel.commons.Strings; @@ -33,15 +33,15 @@ import java.util.Optional; /** - * Stores and manages {@link CustomEventDispatcher custom event dispatchers} in a MongoDB. + * Stores and manages {@link ScriptableEventDispatcher custom event dispatchers} in a MongoDB. */ @Register(framework = MongoCustomEventDispatcherRepository.FRAMEWORK_SCRIPTING_MONGO) -public class MongoCustomEventDispatcherRepository implements CustomEventDispatcherRepository { +public class MongoCustomEventDispatcherRepository implements ScriptableEventDispatcherRepository { protected static final String FRAMEWORK_SCRIPTING_MONGO = "biz.scripting-mongo"; /** - * Contains the name of the variable which holds the {@link CustomEventRegistry} in a script. + * Contains the name of the variable which holds the {@link ScriptableEventRegistry} in a script. */ public static final String SCRIPT_PARAMETER_REGISTRY = "registry"; @@ -60,7 +60,7 @@ public List fetchAvailableDispatchers(@Nonnull String tenantId) { } @Override - public Optional fetchDispatcher(@Nonnull String tenantId, @Nullable String name) { + public Optional fetchDispatcher(@Nonnull String tenantId, @Nullable String name) { if (Strings.isEmpty(name)) { List mongoCustomScripts = mango.select(MongoCustomScript.class).eq(MongoCustomScript.TENANT, tenantId).limit(2).queryList(); @@ -78,7 +78,7 @@ public Optional fetchDispatcher(@Nonnull String tenantId, } } - private Optional compileAndLoad(MongoCustomScript script) { + private Optional compileAndLoad(MongoCustomScript script) { try { if (Strings.isEmpty(script.getScript())) { return Optional.empty(); @@ -88,11 +88,11 @@ private Optional compileAndLoad(MongoCustomScript script) new CompilationContext(SourceCodeInfo.forInlineCode(script.getScript(), SandboxMode.WARN_ONLY)); compilationContext.getVariableScoper() - .defineVariable(Position.UNKNOWN, SCRIPT_PARAMETER_REGISTRY, CustomEventRegistry.class); + .defineVariable(Position.UNKNOWN, SCRIPT_PARAMETER_REGISTRY, ScriptableEventRegistry.class); NoodleCompiler compiler = new NoodleCompiler(compilationContext); Callable compiledScript = compiler.compileScript(); - ScriptBasedCustomEventDispatcher dispatcher = new ScriptBasedCustomEventDispatcher(); + SimpleScriptableEventDispatcher dispatcher = new SimpleScriptableEventDispatcher(); SimpleEnvironment environment = new SimpleEnvironment(); environment.writeVariable(0, dispatcher); compiledScript.call(environment); diff --git a/src/main/java/sirius/biz/scripting/mongo/MongoCustomScriptController.java b/src/main/java/sirius/biz/scripting/mongo/MongoCustomScriptController.java index 73ba642d2..8e97b7ed5 100644 --- a/src/main/java/sirius/biz/scripting/mongo/MongoCustomScriptController.java +++ b/src/main/java/sirius/biz/scripting/mongo/MongoCustomScriptController.java @@ -8,7 +8,7 @@ package sirius.biz.scripting.mongo; -import sirius.biz.scripting.CustomEventRegistry; +import sirius.biz.scripting.ScriptableEventRegistry; import sirius.biz.scripting.ScriptingController; import sirius.biz.web.BizController; import sirius.biz.web.MongoPageHelper; @@ -100,7 +100,7 @@ public void compile(WebContext webContext, JSONStructuredOutput output) { compilationContext.getVariableScoper() .defineVariable(Position.UNKNOWN, MongoCustomEventDispatcherRepository.SCRIPT_PARAMETER_REGISTRY, - CustomEventRegistry.class); + ScriptableEventRegistry.class); NoodleCompiler compiler = new NoodleCompiler(compilationContext); compiler.compileScript(); From f83c8f5f567eed75dacb58427a66f2b7f3e25fda Mon Sep 17 00:00:00 2001 From: Andreas Haufler Date: Tue, 2 Apr 2024 22:46:50 +0200 Subject: [PATCH 4/7] Improvements based on PR review feedback :) --- .../sirius/biz/scripting/ScriptableEvent.java | 11 +++++++++++ .../MongoCustomEventDispatcherRepository.java | 15 +++++++++++---- .../biz/scripting/mongo/MongoCustomScript.java | 3 +++ src/main/resources/component-070-biz.conf | 4 ++++ .../extensions/biz-menu/menu-tenants.html.pasta | 4 +++- 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/main/java/sirius/biz/scripting/ScriptableEvent.java b/src/main/java/sirius/biz/scripting/ScriptableEvent.java index ed84065cf..94d74f53d 100644 --- a/src/main/java/sirius/biz/scripting/ScriptableEvent.java +++ b/src/main/java/sirius/biz/scripting/ScriptableEvent.java @@ -17,8 +17,19 @@ */ public abstract class ScriptableEvent { + /** + * Stores if all event handlers completed successfully. + */ protected boolean success; + + /** + * Stores if an error / exception occurred while invoking an event handler. + */ protected boolean failed; + + /** + * Stores the exception which occurred while invoking an event handler. + */ protected HandledException error; /** diff --git a/src/main/java/sirius/biz/scripting/mongo/MongoCustomEventDispatcherRepository.java b/src/main/java/sirius/biz/scripting/mongo/MongoCustomEventDispatcherRepository.java index fac5cfa28..a27e702a3 100644 --- a/src/main/java/sirius/biz/scripting/mongo/MongoCustomEventDispatcherRepository.java +++ b/src/main/java/sirius/biz/scripting/mongo/MongoCustomEventDispatcherRepository.java @@ -38,7 +38,10 @@ @Register(framework = MongoCustomEventDispatcherRepository.FRAMEWORK_SCRIPTING_MONGO) public class MongoCustomEventDispatcherRepository implements ScriptableEventDispatcherRepository { - protected static final String FRAMEWORK_SCRIPTING_MONGO = "biz.scripting-mongo"; + /** + * Defines the framework which uses MongoDB to store and provide script based event dispatchers. + */ + public static final String FRAMEWORK_SCRIPTING_MONGO = "biz.scripting-mongo"; /** * Contains the name of the variable which holds the {@link ScriptableEventRegistry} in a script. @@ -88,7 +91,9 @@ private Optional compileAndLoad(MongoCustomScript scr new CompilationContext(SourceCodeInfo.forInlineCode(script.getScript(), SandboxMode.WARN_ONLY)); compilationContext.getVariableScoper() - .defineVariable(Position.UNKNOWN, SCRIPT_PARAMETER_REGISTRY, ScriptableEventRegistry.class); + .defineVariable(Position.UNKNOWN, + SCRIPT_PARAMETER_REGISTRY, + ScriptableEventRegistry.class); NoodleCompiler compiler = new NoodleCompiler(compilationContext); Callable compiledScript = compiler.compileScript(); @@ -98,9 +103,11 @@ private Optional compileAndLoad(MongoCustomScript scr compiledScript.call(environment); return Optional.of(dispatcher); - } catch (ScriptingException | HandledException e) { + } catch (ScriptingException | HandledException exception) { TaskContext.get() - .log("Failed compiling custom event dispatcher '%s': %s", script.getCode(), e.getMessage()); + .log("Failed compiling custom event dispatcher '%s': %s", + script.getCode(), + exception.getMessage()); return Optional.empty(); } } diff --git a/src/main/java/sirius/biz/scripting/mongo/MongoCustomScript.java b/src/main/java/sirius/biz/scripting/mongo/MongoCustomScript.java index 4b2276b09..ee3d2c257 100644 --- a/src/main/java/sirius/biz/scripting/mongo/MongoCustomScript.java +++ b/src/main/java/sirius/biz/scripting/mongo/MongoCustomScript.java @@ -13,14 +13,17 @@ import sirius.biz.tenants.mongo.MongoTenantAware; import sirius.biz.web.Autoloaded; import sirius.db.mixing.Mapping; +import sirius.db.mixing.annotations.Index; import sirius.db.mixing.annotations.NullAllowed; import sirius.db.mixing.annotations.Unique; +import sirius.db.mongo.Mango; import sirius.kernel.commons.Strings; import sirius.kernel.nls.NLS; /** * Stores a custom scripting profile for a tenant. */ +@Index(name = "lookup", columns = {"tenant", "code"}, columnSettings = {Mango.INDEX_ASCENDING, Mango.INDEX_ASCENDING}) public class MongoCustomScript extends MongoTenantAware { /** diff --git a/src/main/resources/component-070-biz.conf b/src/main/resources/component-070-biz.conf index e8f05a557..a7a17a402 100644 --- a/src/main/resources/component-070-biz.conf +++ b/src/main/resources/component-070-biz.conf @@ -113,6 +113,10 @@ sirius.frameworks { # Provides a storage option for in a MongoDB. biz.analytics-metrics-mongo = false + # Uses MongoDB to store and manage tenant specific scripts to handle incoming + # script event (e.g. in import processes). + biz.scripting-mongo = false + # Provides an uplink to talk to one or more Jupiter instances. jupiter = false diff --git a/src/main/resources/default/extensions/biz-menu/menu-tenants.html.pasta b/src/main/resources/default/extensions/biz-menu/menu-tenants.html.pasta index f14792a3b..dc6841038 100644 --- a/src/main/resources/default/extensions/biz-menu/menu-tenants.html.pasta +++ b/src/main/resources/default/extensions/biz-menu/menu-tenants.html.pasta @@ -33,6 +33,8 @@ url="/academy" /> - + + + From b72a147038e036e09a9523055ac614a0b5688896 Mon Sep 17 00:00:00 2001 From: Andreas Haufler Date: Tue, 2 Apr 2024 22:53:58 +0200 Subject: [PATCH 5/7] Provides a proper menu entry for MongoCustomScripts --- .../biz-menu/menu-custom-scripts.html.pasta | 12 ++++++++++++ .../extensions/biz-menu/menu-tenants.html.pasta | 4 ---- .../biz-tycho-menu/menu-custom-scripts.html.pasta | 12 ++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 src/main/resources/default/extensions/biz-menu/menu-custom-scripts.html.pasta create mode 100644 src/main/resources/default/extensions/biz-tycho-menu/menu-custom-scripts.html.pasta diff --git a/src/main/resources/default/extensions/biz-menu/menu-custom-scripts.html.pasta b/src/main/resources/default/extensions/biz-menu/menu-custom-scripts.html.pasta new file mode 100644 index 000000000..c306fb58f --- /dev/null +++ b/src/main/resources/default/extensions/biz-menu/menu-custom-scripts.html.pasta @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/src/main/resources/default/extensions/biz-menu/menu-tenants.html.pasta b/src/main/resources/default/extensions/biz-menu/menu-tenants.html.pasta index dc6841038..2cad90ae3 100644 --- a/src/main/resources/default/extensions/biz-menu/menu-tenants.html.pasta +++ b/src/main/resources/default/extensions/biz-menu/menu-tenants.html.pasta @@ -15,7 +15,6 @@ - @@ -33,8 +32,5 @@ url="/academy" /> - - - diff --git a/src/main/resources/default/extensions/biz-tycho-menu/menu-custom-scripts.html.pasta b/src/main/resources/default/extensions/biz-tycho-menu/menu-custom-scripts.html.pasta new file mode 100644 index 000000000..3707ed8b4 --- /dev/null +++ b/src/main/resources/default/extensions/biz-tycho-menu/menu-custom-scripts.html.pasta @@ -0,0 +1,12 @@ + + + + + + + + + + From b157759d4ecf22081424bb0e37ac4bb597af7dae Mon Sep 17 00:00:00 2001 From: Andreas Haufler Date: Tue, 2 Apr 2024 22:54:07 +0200 Subject: [PATCH 6/7] Code formatting --- .../extensions/biz-tycho-menu/menu-tenants.html.pasta | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/resources/default/extensions/biz-tycho-menu/menu-tenants.html.pasta b/src/main/resources/default/extensions/biz-tycho-menu/menu-tenants.html.pasta index 8bd2601ca..30b30642f 100644 --- a/src/main/resources/default/extensions/biz-tycho-menu/menu-tenants.html.pasta +++ b/src/main/resources/default/extensions/biz-tycho-menu/menu-tenants.html.pasta @@ -8,14 +8,13 @@ + permission="permission-manage-system-users"/> + permission="permission-manage-user-accounts"/> - @@ -25,12 +24,12 @@ + permission="permission-select-user-account"/> + url="/academy"/> From ef5f46a73c2c373bf8c0c578782d824b5436f8bd Mon Sep 17 00:00:00 2001 From: Andreas Haufler Date: Tue, 2 Apr 2024 22:56:06 +0200 Subject: [PATCH 7/7] Marks tenants using custom scripts We're probably going to copy/paste ("get some inspiration") a lot here, therefore it's nice to know which tenant uses scripts. --- .../mongo/MongoTenantMetricComputer.java | 25 ++++++++++++++++++- src/main/resources/biz_de.properties | 1 + 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/main/java/sirius/biz/tenants/mongo/MongoTenantMetricComputer.java b/src/main/java/sirius/biz/tenants/mongo/MongoTenantMetricComputer.java index 46c1a0918..8a0fbd5a9 100644 --- a/src/main/java/sirius/biz/tenants/mongo/MongoTenantMetricComputer.java +++ b/src/main/java/sirius/biz/tenants/mongo/MongoTenantMetricComputer.java @@ -9,11 +9,15 @@ package sirius.biz.tenants.mongo; import sirius.biz.analytics.flags.PerformanceFlag; +import sirius.biz.analytics.metrics.MetricComputerContext; import sirius.biz.model.LoginData; +import sirius.biz.scripting.mongo.MongoCustomEventDispatcherRepository; +import sirius.biz.scripting.mongo.MongoCustomScript; import sirius.biz.tenants.UserAccount; import sirius.biz.tenants.UserAccountData; import sirius.biz.tenants.metrics.computers.TenantMetricComputer; import sirius.db.mongo.Mango; +import sirius.kernel.Sirius; import sirius.kernel.di.std.Part; import sirius.kernel.di.std.Register; @@ -32,14 +36,33 @@ public class MongoTenantMetricComputer extends TenantMetricComputer PerformanceFlag.register(MongoTenant.class, "active-users", 0).makeVisible().markAsFilter(); /** - * Marks tenants which hase users that use the video academy. + * Marks tenants which have users that use the video academy. */ public static final PerformanceFlag ACADEMY_USERS = PerformanceFlag.register(MongoTenant.class, "academy-users", 1).makeVisible().markAsFilter(); + /** + * Marks tenants which have at least one {@link MongoCustomScript custom script}. + */ + public static final PerformanceFlag CUSTOM_SCRIPTS = + PerformanceFlag.register(MongoTenant.class, "custom-scripts", 2).makeVisible().markAsFilter(); + @Part private Mango mango; + @Override + public void compute(MetricComputerContext context, MongoTenant tenant) throws Exception { + super.compute(context, tenant); + + if (Sirius.isFrameworkEnabled(MongoCustomEventDispatcherRepository.FRAMEWORK_SCRIPTING_MONGO)) { + tenant.getPerformanceData() + .modify() + .set(CUSTOM_SCRIPTS, + mango.select(MongoCustomScript.class).eq(MongoCustomScript.TENANT, tenant.getId()).exists()) + .commit(); + } + } + @Override protected PerformanceFlag getAcademyUsersFlag() { return ACADEMY_USERS; diff --git a/src/main/resources/biz_de.properties b/src/main/resources/biz_de.properties index cb398392a..fe5cd348d 100644 --- a/src/main/resources/biz_de.properties +++ b/src/main/resources/biz_de.properties @@ -751,6 +751,7 @@ Parameter.required = Der Parameter ${name} muss gefüllt sein. PerformanceData.flags = Flags PerformanceFlag.academy-user = Nutzt Video-Academy PerformanceFlag.academy-users = Nutzt Video-Academy +PerformanceFlag.custom-scripts = Nutzt Kunden-Scripting PerformanceFlag.active-user = Aktiv PerformanceFlag.active-users = Hat aktive Nutzer PerformanceFlag.frequent-user = Regelmäßig Aktiv