From e79557c2a8f8210401b0cbada7d5d819311c8a5a Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Tue, 26 May 2026 15:26:07 +0200 Subject: [PATCH 1/4] add html pipeline for html lots --- .../condation/cms/api/hooks/HookSystem.java | 2 - .../com/condation/cms/api/hooks/Hooks.java | 2 + .../cms/content/DefaultContentRenderer.java | 6 +- .../cms/content/pipeline/ContentPipeline.java | 2 +- .../cms/content/pipeline/HTMLPipeline.java | 68 ++++++++ .../content/pipeline/HTMLPipelineTest.java | 159 ++++++++++++++++++ .../themes/demo/extensions/theme.extension.js | 7 + 7 files changed, 242 insertions(+), 4 deletions(-) create mode 100644 cms-content/src/main/java/com/condation/cms/content/pipeline/HTMLPipeline.java create mode 100644 cms-content/src/test/java/com/condation/cms/content/pipeline/HTMLPipelineTest.java diff --git a/cms-api/src/main/java/com/condation/cms/api/hooks/HookSystem.java b/cms-api/src/main/java/com/condation/cms/api/hooks/HookSystem.java index 896486ab..1f05a49c 100644 --- a/cms-api/src/main/java/com/condation/cms/api/hooks/HookSystem.java +++ b/cms-api/src/main/java/com/condation/cms/api/hooks/HookSystem.java @@ -25,12 +25,10 @@ import com.condation.cms.api.utils.AnnotationsUtil; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; /** diff --git a/cms-api/src/main/java/com/condation/cms/api/hooks/Hooks.java b/cms-api/src/main/java/com/condation/cms/api/hooks/Hooks.java index cffc774e..a1018578 100644 --- a/cms-api/src/main/java/com/condation/cms/api/hooks/Hooks.java +++ b/cms-api/src/main/java/com/condation/cms/api/hooks/Hooks.java @@ -34,6 +34,8 @@ public enum Hooks { /* content */ CONTENT_TAGS("system/content/tags"), CONTENT_FILTER("system/content/filter"), + CONTENT_SLOT_HEADER("system/content/slot/header"), + CONTENT_SLOT_FOOTER("system/content/slot/footer"), /*query*/ DB_QUERY_OPERATIONS("system/db/query/operations"), /* scheduler */ diff --git a/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java b/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java index 0583ee5e..7b3899fd 100644 --- a/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java +++ b/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java @@ -49,6 +49,7 @@ import com.condation.cms.content.pipeline.ContentPipelineFactory; import com.condation.cms.content.views.model.View; import com.condation.cms.api.content.MapAccess; +import com.condation.cms.content.pipeline.HTMLPipeline; import com.condation.cms.extensions.hooks.DBHooks; import com.condation.cms.extensions.hooks.TemplateHooks; import com.condation.cms.content.template.functions.LinkFunction; @@ -218,7 +219,10 @@ public String render(final ReadOnlyFile contentFile, final RequestContext contex model.values.putAll(namespace.getNamespaces()); - return templates.get().render((String) meta.get("template"), model); + var htmlContent = templates.get().render((String) meta.get("template"), model); + + HTMLPipeline htmlPipeline = new HTMLPipeline(context.get(HookSystemFeature.class).hookSystem()); + return htmlPipeline.process(htmlContent); } protected MarkdownFunction createMarkdownFunction(final RequestContext context) { diff --git a/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipeline.java b/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipeline.java index bf542bb1..17a9c627 100644 --- a/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipeline.java +++ b/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipeline.java @@ -63,7 +63,7 @@ protected void init() { }); } - + public String process(String rawContent) { return hookSystem.filter(Hooks.CONTENT_FILTER.hook(), rawContent).value(); } diff --git a/cms-content/src/main/java/com/condation/cms/content/pipeline/HTMLPipeline.java b/cms-content/src/main/java/com/condation/cms/content/pipeline/HTMLPipeline.java new file mode 100644 index 00000000..e0fffed2 --- /dev/null +++ b/cms-content/src/main/java/com/condation/cms/content/pipeline/HTMLPipeline.java @@ -0,0 +1,68 @@ +package com.condation.cms.content.pipeline; + +/*- + * #%L + * CMS Content + * %% + * Copyright (C) 2023 - 2026 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * #L% + */ +import com.condation.cms.api.hooks.HookSystem; +import com.condation.cms.api.hooks.Hooks; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * + * @author thmar + */ +@Slf4j +@RequiredArgsConstructor +public class HTMLPipeline { + + private final HookSystem hookSystem; + + public String process(String rawContent) { + rawContent = updateSlot(Hooks.CONTENT_SLOT_HEADER, "", rawContent); + return updateSlot(Hooks.CONTENT_SLOT_FOOTER, "", rawContent); + } + + public String updateSlot (Hooks hook, String elementName, String rawContent) { + + if (!rawContent.contains(elementName)) { + log.warn("No {} found, skipping header slot injection", elementName); + return rawContent; + } + + var result = hookSystem.execute(hook.hook()); + + var hookValues = result.results().stream() + .filter(Objects::nonNull) + .filter(String.class::isInstance) + .map(String.class::cast) + .toList(); + if (hookValues.isEmpty()) { + return rawContent; + } + + + var mergedValue = String.join("\n\n", hookValues); + + return rawContent.replace(elementName, mergedValue + "\n" + elementName); + } + +} diff --git a/cms-content/src/test/java/com/condation/cms/content/pipeline/HTMLPipelineTest.java b/cms-content/src/test/java/com/condation/cms/content/pipeline/HTMLPipelineTest.java new file mode 100644 index 00000000..d23d0390 --- /dev/null +++ b/cms-content/src/test/java/com/condation/cms/content/pipeline/HTMLPipelineTest.java @@ -0,0 +1,159 @@ +package com.condation.cms.content.pipeline; + +/*- + * #%L + * CMS Content + * %% + * Copyright (C) 2023 - 2026 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * #L% + */ +import com.condation.cms.api.hooks.HookSystem; +import com.condation.cms.api.hooks.Hooks; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class HTMLPipelineTest { + + private HookSystem hookSystem; + private HTMLPipeline pipeline; + + @BeforeEach + void setUp() { + hookSystem = new HookSystem(); + pipeline = new HTMLPipeline(hookSystem); + } + + @Test + void shouldInjectHeaderSlot() { + // given + String html = """ + + + + + content + + + """; + + hookSystem.registerAction(Hooks.CONTENT_SLOT_HEADER.hook(), + (context) -> ""); + // when + String result = pipeline.process(html); + + // then + assertThat(result) + .contains("") + .contains(""); + } + + @Test + void shouldInjectFooterSlot() { + // given + String html = """ + + + + + content + + + """; + + hookSystem.registerAction(Hooks.CONTENT_SLOT_FOOTER.hook(), + (context) -> ""); + + // when + String result = pipeline.process(html); + + // then + assertThat(result) + .contains("") + .contains(""); + } + + @Test + void shouldInjectBothSlots() { + // given + String html = """ + + + + + content + + + """; + + hookSystem.registerAction(Hooks.CONTENT_SLOT_HEADER.hook(), + (context) -> ""); + hookSystem.registerAction(Hooks.CONTENT_SLOT_FOOTER.hook(), + (context) -> ""); + + // when + String result = pipeline.process(html); + + // then + assertThat(result) + .contains("") + .contains("") + .contains("") + .contains(""); + } + + @Test + void shouldIgnoreEmptyHooks() { + // given + String html = """ + + + + + content + + + """; + + // when + String result = pipeline.process(html); + + // then + assertThat(result) + .doesNotContain("") + .contains(""); + } + + @Test + void shouldNotFailIfNoHeadOrBodyExists() { + // given + String html = "
no layout tags
"; + + hookSystem.registerAction(Hooks.CONTENT_SLOT_HEADER.hook(), + (context) -> ""); + hookSystem.registerAction(Hooks.CONTENT_SLOT_FOOTER.hook(), + (context) -> ""); + + // when + String result = pipeline.process(html); + + // then + assertThat(result) + .isEqualTo(html); + } +} diff --git a/test-server/themes/demo/extensions/theme.extension.js b/test-server/themes/demo/extensions/theme.extension.js index 6b5a4947..f095295b 100644 --- a/test-server/themes/demo/extensions/theme.extension.js +++ b/test-server/themes/demo/extensions/theme.extension.js @@ -51,4 +51,11 @@ $hooks.registerFilter("module/ui/translations", (context) => { translations.de["field.description"] = "Beschreibung"; return translations; +}) + +$hooks.registerAction("system/content/slot/header", (context) => { + return ""; +}) +$hooks.registerAction("system/content/slot/footer", (context) => { + return ""; }) \ No newline at end of file From e3a981fa05328000745d2954c6f0df3eacb6a515 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Tue, 26 May 2026 15:59:34 +0200 Subject: [PATCH 2/4] rework on the hooksystem --- .../com/condation/cms/api/hooks/HookSystem.java | 8 ++++---- .../condation/cms/api/hooks/HookSystemTest.java | 16 ++++++++-------- .../cms/content/pipeline/ContentPipeline.java | 2 +- .../cms/content/pipeline/HTMLPipeline.java | 2 +- .../functions/hooks/HooksTemlateFunction.java | 4 ++-- .../functions/navigation/NavigationFunction.java | 4 ++-- .../cms/extensions/hooks/ContentHooks.java | 2 +- .../condation/cms/extensions/hooks/DBHooks.java | 2 +- .../cms/extensions/hooks/GlobalHooks.java | 4 ++-- .../cms/extensions/hooks/ServerHooks.java | 6 +++--- .../cms/extensions/hooks/TemplateHooks.java | 6 +++--- .../cms/extensions/ExtensionManagerTest.java | 4 ++-- .../cms/extensions/GlobalExtensionsTest.java | 2 +- .../ExampleJettyHttpHandlerExtension.java | 2 +- .../HooksTemplateFunctionExtensions.java | 2 +- .../cms/modules/ui/http/HookHandler.java | 2 +- .../condation/cms/modules/ui/utils/UIHooks.java | 8 ++++---- 17 files changed, 38 insertions(+), 38 deletions(-) diff --git a/cms-api/src/main/java/com/condation/cms/api/hooks/HookSystem.java b/cms-api/src/main/java/com/condation/cms/api/hooks/HookSystem.java index 1f05a49c..efc7a4e3 100644 --- a/cms-api/src/main/java/com/condation/cms/api/hooks/HookSystem.java +++ b/cms-api/src/main/java/com/condation/cms/api/hooks/HookSystem.java @@ -88,11 +88,11 @@ public void registerFilter(final String name, final FilterFunction hookFu filters.put(name, new FilterHook<>(name, priority, hookFunction)); } - public ActionContext execute(final String name) { - return execute(name, Map.of()); + public ActionContext doAction(final String name) { + return HookSystem.this.doAction(name, Map.of()); } - public ActionContext execute(final String name, final Map arguments) { + public ActionContext doAction(final String name, final Map arguments) { var context = new ActionContext(new HashMap<>(arguments), new ArrayList<>()); actions.get(name).stream() .sorted((h1, h2) -> Integer.compare(h1.priority(), h2.priority())) @@ -119,7 +119,7 @@ public ActionContext execute(final String name, final Map FilterContext filter(final String name, final T parameters) { + public FilterContext doFilter(final String name, final T parameters) { final FilterContext returnContext = new FilterContext( parameters ); diff --git a/cms-api/src/test/java/com/condation/cms/api/hooks/HookSystemTest.java b/cms-api/src/test/java/com/condation/cms/api/hooks/HookSystemTest.java index cfb3cbf3..5b46ed5d 100644 --- a/cms-api/src/test/java/com/condation/cms/api/hooks/HookSystemTest.java +++ b/cms-api/src/test/java/com/condation/cms/api/hooks/HookSystemTest.java @@ -49,7 +49,7 @@ public void test_single_result() { hookSystem.registerAction("test/test1", (context) -> { return true; }); - var context = hookSystem.execute("test/test1"); + var context = hookSystem.doAction("test/test1"); Assertions.assertThat(context.results()).hasSize(1).contains(true); } @@ -61,7 +61,7 @@ public void test_multiple_result() { hookSystem.registerAction("test/test1", (context) -> { return "test2"; }); - var context = hookSystem.execute("test/test1"); + var context = hookSystem.doAction("test/test1"); Assertions.assertThat(context.results()).hasSize(2).contains("test1", "test2"); } @@ -76,7 +76,7 @@ public void test_multiple_result_with_priority() { hookSystem.registerAction("test/test1", (context) -> { return "test2"; }, 200); - var context = hookSystem.execute("test/test1"); + var context = hookSystem.doAction("test/test1"); Assertions.assertThat(context.results()).hasSize(3).containsExactly("test1", "test2", "test3"); } @@ -91,14 +91,14 @@ public void test_multiple_result_with_priority_reversed() { hookSystem.registerAction("test/test1", (context) -> { return "test2"; }, 200); - var context = hookSystem.execute("test/test1"); + var context = hookSystem.doAction("test/test1"); Assertions.assertThat(context.results()).hasSize(3).containsExactly("test3", "test2", "test1"); } @Test public void test_filter_reversed () { hookSystem.registerFilter("test/list", (context) -> ((List)context.value()).reversed()); - var context = hookSystem.filter("test/list", List.of("1", "2", "3")); + var context = hookSystem.doFilter("test/list", List.of("1", "2", "3")); Assertions.assertThat(context.value()).containsExactly("3", "2", "1"); } @@ -108,7 +108,7 @@ public void test_filter_remove () { context.value().remove("2"); return context.value(); }); - var context = hookSystem.filter("test/list", new ArrayList<>(List.of("1", "2", "3"))); + var context = hookSystem.doFilter("test/list", new ArrayList<>(List.of("1", "2", "3"))); Assertions.assertThat(context.value()).containsExactly("1", "3"); } @@ -118,7 +118,7 @@ void test_action_annotation () { hookSystem.register(actionObject); - hookSystem.execute("test/annotation/action1"); + hookSystem.doAction("test/annotation/action1"); Assertions.assertThat(actionObject.counter).hasValue(2); } @@ -129,7 +129,7 @@ void test_filter_annotations () { hookSystem.register(myFilters); - var context = hookSystem.filter("test/annotation/filter1", new ArrayList<>(List.of("1", "2", "3"))); + var context = hookSystem.doFilter("test/annotation/filter1", new ArrayList<>(List.of("1", "2", "3"))); Assertions.assertThat(context.value()).containsExactly("1", "3"); } diff --git a/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipeline.java b/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipeline.java index 17a9c627..a0bb9212 100644 --- a/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipeline.java +++ b/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipeline.java @@ -65,7 +65,7 @@ protected void init() { } public String process(String rawContent) { - return hookSystem.filter(Hooks.CONTENT_FILTER.hook(), rawContent).value(); + return hookSystem.doFilter(Hooks.CONTENT_FILTER.hook(), rawContent).value(); } private String processMarkdown(FilterContext context) { diff --git a/cms-content/src/main/java/com/condation/cms/content/pipeline/HTMLPipeline.java b/cms-content/src/main/java/com/condation/cms/content/pipeline/HTMLPipeline.java index e0fffed2..0d81892e 100644 --- a/cms-content/src/main/java/com/condation/cms/content/pipeline/HTMLPipeline.java +++ b/cms-content/src/main/java/com/condation/cms/content/pipeline/HTMLPipeline.java @@ -48,7 +48,7 @@ public String updateSlot (Hooks hook, String elementName, String rawContent) { return rawContent; } - var result = hookSystem.execute(hook.hook()); + var result = hookSystem.doAction(hook.hook()); var hookValues = result.results().stream() .filter(Objects::nonNull) diff --git a/cms-content/src/main/java/com/condation/cms/content/template/functions/hooks/HooksTemlateFunction.java b/cms-content/src/main/java/com/condation/cms/content/template/functions/hooks/HooksTemlateFunction.java index 6bcead54..fd25f0b0 100644 --- a/cms-content/src/main/java/com/condation/cms/content/template/functions/hooks/HooksTemlateFunction.java +++ b/cms-content/src/main/java/com/condation/cms/content/template/functions/hooks/HooksTemlateFunction.java @@ -42,13 +42,13 @@ public ActionContext execute (String name) { return execute(name, Map.of()); } public ActionContext execute (String name, Map arguments) { - return hookSystem.execute(name, arguments); + return hookSystem.doAction(name, arguments); } public FilterContext filter (String name) { return filter(name, List.of()); } public FilterContext filter (String name, List arguments) { - return hookSystem.filter(name, arguments); + return hookSystem.doFilter(name, arguments); } } diff --git a/cms-content/src/main/java/com/condation/cms/content/template/functions/navigation/NavigationFunction.java b/cms-content/src/main/java/com/condation/cms/content/template/functions/navigation/NavigationFunction.java index 611572d8..cc33fa3f 100644 --- a/cms-content/src/main/java/com/condation/cms/content/template/functions/navigation/NavigationFunction.java +++ b/cms-content/src/main/java/com/condation/cms/content/template/functions/navigation/NavigationFunction.java @@ -104,7 +104,7 @@ public List path() { navNodes = navNodes.reversed(); if (name != null) { - var hookContext = hookSystem.filter(Hooks.NAVIGATION_PATH.hook(name), navNodes); + var hookContext = hookSystem.doFilter(Hooks.NAVIGATION_PATH.hook(name), navNodes); navNodes = hookContext.value(); } @@ -153,7 +153,7 @@ private List getNodes(final String start, final int depth) { } if (name != null) { - var hookContext = hookSystem.filter(Hooks.NAVIGATION_LIST.hook(name), navNodes); + var hookContext = hookSystem.doFilter(Hooks.NAVIGATION_LIST.hook(name), navNodes); navNodes = hookContext.value(); } return navNodes; diff --git a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/ContentHooks.java b/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/ContentHooks.java index b5d1d99c..fbdf2453 100644 --- a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/ContentHooks.java +++ b/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/ContentHooks.java @@ -47,7 +47,7 @@ public class ContentHooks implements Feature { public TagsWrapper getTags (Map> codes) { var codeWrapper = new TagsWrapper(codes); requestContext.get(HookSystemFeature.class).hookSystem() - .execute(Hooks.CONTENT_TAGS.hook(), Map.of("tags", codeWrapper)); + .doAction(Hooks.CONTENT_TAGS.hook(), Map.of("tags", codeWrapper)); return codeWrapper; } } diff --git a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/DBHooks.java b/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/DBHooks.java index b66d003e..2b3e331e 100644 --- a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/DBHooks.java +++ b/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/DBHooks.java @@ -46,7 +46,7 @@ public class DBHooks implements Feature { public QueryOperationsWrapper getQueryOperations () { var wrapper = new QueryOperationsWrapper(); requestContext.get(HookSystemFeature.class).hookSystem() - .execute(Hooks.DB_QUERY_OPERATIONS.hook(), Map.of("operations", wrapper)); + .doAction(Hooks.DB_QUERY_OPERATIONS.hook(), Map.of("operations", wrapper)); return wrapper; } diff --git a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/GlobalHooks.java b/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/GlobalHooks.java index d8c203a5..5258c05b 100644 --- a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/GlobalHooks.java +++ b/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/GlobalHooks.java @@ -39,9 +39,9 @@ public class GlobalHooks { private final CronJobScheduler scheduler; public void registerCronJob () { - globalHookSystem.execute(Hooks.SCHEDULER_REGISTER.hook(), Map.of("scheduler", scheduler)); + globalHookSystem.doAction(Hooks.SCHEDULER_REGISTER.hook(), Map.of("scheduler", scheduler)); } public void removeCronJob () { - globalHookSystem.execute(Hooks.SCHEDULER_REMOVE.hook(), Map.of("scheduler", scheduler)); + globalHookSystem.doAction(Hooks.SCHEDULER_REMOVE.hook(), Map.of("scheduler", scheduler)); } } diff --git a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/ServerHooks.java b/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/ServerHooks.java index a2341b98..4094809b 100644 --- a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/ServerHooks.java +++ b/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/ServerHooks.java @@ -46,7 +46,7 @@ public class ServerHooks implements Feature { public HttpHandlerWrapper getHttpExtensions () { var httpExtensions = new HttpHandlerWrapper(); requestContext.get(HookSystemFeature.class).hookSystem() - .execute(Hooks.HTTP_EXTENSION.hook(), Map.of("httpExtensions", httpExtensions)); + .doAction(Hooks.HTTP_EXTENSION.hook(), Map.of("httpExtensions", httpExtensions)); return httpExtensions; } @@ -54,7 +54,7 @@ public HttpHandlerWrapper getHttpExtensions () { public HttpHandlerWrapper getHttpRoutes () { var httpExtensions = new HttpHandlerWrapper(); requestContext.get(HookSystemFeature.class).hookSystem() - .execute(Hooks.HTTP_ROUTE.hook(), Map.of("httpRoutes", httpExtensions)); + .doAction(Hooks.HTTP_ROUTE.hook(), Map.of("httpRoutes", httpExtensions)); return httpExtensions; } @@ -62,7 +62,7 @@ public HttpHandlerWrapper getHttpRoutes () { public HttpHandlerWrapper getAPIRoutes () { var httpExtensions = new HttpHandlerWrapper(); requestContext.get(HookSystemFeature.class).hookSystem() - .execute(Hooks.API_ROUTE.hook(), Map.of("apiRoutes", httpExtensions)); + .doAction(Hooks.API_ROUTE.hook(), Map.of("apiRoutes", httpExtensions)); return httpExtensions; } diff --git a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TemplateHooks.java b/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TemplateHooks.java index 93c096c6..946997a9 100644 --- a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TemplateHooks.java +++ b/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TemplateHooks.java @@ -48,7 +48,7 @@ public class TemplateHooks implements Feature { public TemplateSupplierWrapper getTemplateSupplier () { var templateSupplier = new TemplateSupplierWrapper(); requestContext.get(HookSystemFeature.class).hookSystem() - .execute(Hooks.TEMPLATE_SUPPLIER.hook(), Map.of("suppliers", templateSupplier)); + .doAction(Hooks.TEMPLATE_SUPPLIER.hook(), Map.of("suppliers", templateSupplier)); return templateSupplier; } @@ -56,7 +56,7 @@ public TemplateSupplierWrapper getTemplateSupplier () { public TemplateFunctionWrapper getTemplateFunctions () { var templateFunctions = new TemplateFunctionWrapper(); requestContext.get(HookSystemFeature.class).hookSystem() - .execute(Hooks.TEMPLATE_FUNCTION.hook(), Map.of("functions", templateFunctions)); + .doAction(Hooks.TEMPLATE_FUNCTION.hook(), Map.of("functions", templateFunctions)); return templateFunctions; } @@ -64,7 +64,7 @@ public TemplateFunctionWrapper getTemplateFunctions () { public TemplateComponentsWrapper getComponents (Map> components) { var componentsWrapper = new TemplateComponentsWrapper(components); requestContext.get(HookSystemFeature.class).hookSystem() - .execute(Hooks.TEMPLATE_COMPONENT.hook(), Map.of("components", componentsWrapper)); + .doAction(Hooks.TEMPLATE_COMPONENT.hook(), Map.of("components", componentsWrapper)); return componentsWrapper; } diff --git a/cms-extensions/src/test/java/com/condation/cms/extensions/ExtensionManagerTest.java b/cms-extensions/src/test/java/com/condation/cms/extensions/ExtensionManagerTest.java index 9845c4a8..14a11bf4 100644 --- a/cms-extensions/src/test/java/com/condation/cms/extensions/ExtensionManagerTest.java +++ b/cms-extensions/src/test/java/com/condation/cms/extensions/ExtensionManagerTest.java @@ -96,7 +96,7 @@ public void test_with_auth() throws IOException { requestContext.add(AuthFeature.class, new AuthFeature("thorsten")); extensionManager.newContext(theme, requestContext); - Assertions.assertThat(hookSystem.execute("test").results()) + Assertions.assertThat(hookSystem.doAction("test").results()) .hasSize(1) .containsExactly("Hallo thorsten"); } @@ -109,7 +109,7 @@ public void test_without_auth() throws IOException { requestContext.add(HookSystemFeature.class, new HookSystemFeature(hookSystem)); extensionManager.newContext(theme, requestContext); - Assertions.assertThat(hookSystem.execute("test").results()) + Assertions.assertThat(hookSystem.doAction("test").results()) .hasSize(1) .containsExactly("Guten Tag"); } diff --git a/cms-extensions/src/test/java/com/condation/cms/extensions/GlobalExtensionsTest.java b/cms-extensions/src/test/java/com/condation/cms/extensions/GlobalExtensionsTest.java index d690be3d..c1521243 100644 --- a/cms-extensions/src/test/java/com/condation/cms/extensions/GlobalExtensionsTest.java +++ b/cms-extensions/src/test/java/com/condation/cms/extensions/GlobalExtensionsTest.java @@ -70,7 +70,7 @@ public void test_init() { public void test_hook () { globalExtensions.evaluate("$hooks.registerAction('test/hook1', (context) => {return 'Hello';})"); - var context = hookSystem.execute("test/hook1"); + var context = hookSystem.doAction("test/hook1"); Assertions.assertThat(context.results()) .hasSize(1) diff --git a/modules/example-module/src/main/java/com/condation/cms/modules/example/ExampleJettyHttpHandlerExtension.java b/modules/example-module/src/main/java/com/condation/cms/modules/example/ExampleJettyHttpHandlerExtension.java index cddd2d2a..8d8b35d9 100644 --- a/modules/example-module/src/main/java/com/condation/cms/modules/example/ExampleJettyHttpHandlerExtension.java +++ b/modules/example-module/src/main/java/com/condation/cms/modules/example/ExampleJettyHttpHandlerExtension.java @@ -50,7 +50,7 @@ public Mapping getMapping() { mapping.add(PathSpec.from("/people"), new ExampleHandler("Hello people!")); mapping.add(PathSpec.from("/hook"), (request, response, callback) -> { - ActionContext hookContext = getRequestContext().get(HookSystemFeature.class).hookSystem().execute("example/test"); + ActionContext hookContext = getRequestContext().get(HookSystemFeature.class).hookSystem().doAction("example/test"); var content = (String)hookContext.results().get(0); diff --git a/modules/system-modules/src/main/java/com/condation/cms/modules/system/templates/HooksTemplateFunctionExtensions.java b/modules/system-modules/src/main/java/com/condation/cms/modules/system/templates/HooksTemplateFunctionExtensions.java index 6186b265..01cd8850 100644 --- a/modules/system-modules/src/main/java/com/condation/cms/modules/system/templates/HooksTemplateFunctionExtensions.java +++ b/modules/system-modules/src/main/java/com/condation/cms/modules/system/templates/HooksTemplateFunctionExtensions.java @@ -49,7 +49,7 @@ public Object hook(Parameter params){ return ""; } - List results = getRequestContext().get(HookSystemFeature.class).hookSystem().execute(hook).results().stream() + List results = getRequestContext().get(HookSystemFeature.class).hookSystem().doAction(hook).results().stream() .map(Object::toString) // oder o -> o.getName() .toList(); diff --git a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/http/HookHandler.java b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/http/HookHandler.java index 8357c083..4e467cba 100644 --- a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/http/HookHandler.java +++ b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/http/HookHandler.java @@ -55,7 +55,7 @@ public boolean handle(Request request, Response response, Callback callback) thr String body = getBody(request); var command = UIGsonProvider.INSTANCE.fromJson(body, HookCall.class); - ActionContext actionContext = hookSystem.execute(command.hook(), command.parameters()); + ActionContext actionContext = hookSystem.doAction(command.hook(), command.parameters()); Map commandResponse = new HashMap<>(); commandResponse.put("hook", command.hook()); diff --git a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/utils/UIHooks.java b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/utils/UIHooks.java index c0d9bf1b..3f873243 100644 --- a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/utils/UIHooks.java +++ b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/utils/UIHooks.java @@ -47,19 +47,19 @@ public UIHooks (final HookSystem hookSystem) { public ContentTypes contentTypes () { var contentTypes = new ContentTypes(); - return hookSystem.filter(HOOK_REGISTER_CONTENT_TYPES, contentTypes).value(); + return hookSystem.doFilter(HOOK_REGISTER_CONTENT_TYPES, contentTypes).value(); } public MediaForms mediaForms () { var mediaForms = new MediaForms(); - return hookSystem.filter(HOOK_REGISTER_MEDIA_FORMS, mediaForms).value(); + return hookSystem.doFilter(HOOK_REGISTER_MEDIA_FORMS, mediaForms).value(); } public Menu menu() { var menu = new Menu(); - menu = hookSystem.filter(HOOK_MENU, menu).value(); + menu = hookSystem.doFilter(HOOK_MENU, menu).value(); return menu; } @@ -69,6 +69,6 @@ public Map> translations () { "de", new HashMap<>(), "en", new HashMap<>() )); - return hookSystem.filter(HOOK_TRANSLATIONS, translations).value(); + return hookSystem.doFilter(HOOK_TRANSLATIONS, translations).value(); } } From 0448fee4e0a23e39c4037d500a7cc036eef992ed Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Tue, 26 May 2026 16:52:37 +0200 Subject: [PATCH 3/4] rework on the hooksystem, moved to new module --- .../condation/cms/api/hooks/HookSystem.java | 100 ++---------- cms-content/pom.xml | 2 +- .../cms/content/DefaultContentRenderer.java | 4 +- .../pipeline/ContentPipelineFactory.java | 5 +- .../content/pipeline/HTMLPipelineTest.java | 3 +- .../navigation/NavigationFunctionITest.java | 3 +- .../navigation/NavigationFunctionTest.java | 3 +- cms-extensions/pom.xml | 1 - cms-hooksystem/pom.xml | 29 ++++ .../condation/cms/hooksystem}/ActionHook.java | 4 +- .../cms/hooksystem/CMSHookSystem.java | 145 ++++++++++++++++++ .../condation/cms/hooksystem}/FilterHook.java | 4 +- .../com/condation/cms/hooksystem}/Hook.java | 2 +- .../hooksystem/extensions}/ContentHooks.java | 2 +- .../cms/hooksystem/extensions}/DBHooks.java | 2 +- .../hooksystem/extensions}/GlobalHooks.java | 2 +- .../extensions}/HttpHandlerWrapper.java | 2 +- .../extensions}/QueryOperationsWrapper.java | 2 +- .../hooksystem/extensions}/ServerHooks.java | 2 +- .../hooksystem/extensions}/TagsWrapper.java | 2 +- .../TemplateComponentsWrapper.java | 2 +- .../extensions}/TemplateFunctionWrapper.java | 2 +- .../hooksystem/extensions}/TemplateHooks.java | 2 +- .../extensions}/TemplateSupplierWrapper.java | 2 +- .../cms/hooksystem/CMSHookSystemTest.java | 10 +- .../extensions/ExtensionManagerTest.java | 8 +- .../extensions/GlobalExtensionsTest.java | 7 +- .../resources/site/extensions/hook-test.js | 0 cms-server/pom.xml | 4 + .../cms/request/RequestContextFactory.java | 9 +- .../server/configs/ServerGlobalModule.java | 3 +- .../cms/server/configs/SiteGlobalModule.java | 5 +- .../cms/server/configs/SiteModulesModule.java | 3 +- .../JettyHttpHandlerExtensionHandler.java | 3 +- .../cms/server/handler/http/APIHandler.java | 4 +- .../server/handler/http/RoutesHandler.java | 4 +- .../com/condation/cms/server/host/VHost.java | 2 +- cms-templates/pom.xml | 5 + .../cms/templates/DynamicConfiguration.java | 2 +- .../module/CMSModuleTemplateEngine.java | 2 +- .../TemplateEngineFunctionsTest.java | 5 +- integration-tests/pom.xml | 4 + .../java/com/condation/cms/TestHelper.java | 9 +- modules/ui-module/pom.xml | 4 + .../modules/ui/utils/TemplateEngineTest.java | 3 +- .../cms/modules/ui/utils/UIHooksTest.java | 5 +- pom.xml | 6 + .../libs/example-module-8.0.0.jar | Bin 19536 -> 0 bytes .../libs/example-module-8.1.0.jar | Bin 0 -> 19993 bytes .../modules/example-module/module.properties | 2 +- 50 files changed, 282 insertions(+), 154 deletions(-) create mode 100644 cms-hooksystem/pom.xml rename {cms-api/src/main/java/com/condation/cms/api/hooks => cms-hooksystem/src/main/java/com/condation/cms/hooksystem}/ActionHook.java (90%) create mode 100644 cms-hooksystem/src/main/java/com/condation/cms/hooksystem/CMSHookSystem.java rename {cms-api/src/main/java/com/condation/cms/api/hooks => cms-hooksystem/src/main/java/com/condation/cms/hooksystem}/FilterHook.java (90%) rename {cms-api/src/main/java/com/condation/cms/api/hooks => cms-hooksystem/src/main/java/com/condation/cms/hooksystem}/Hook.java (95%) rename {cms-extensions/src/main/java/com/condation/cms/extensions/hooks => cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions}/ContentHooks.java (97%) rename {cms-extensions/src/main/java/com/condation/cms/extensions/hooks => cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions}/DBHooks.java (97%) rename {cms-extensions/src/main/java/com/condation/cms/extensions/hooks => cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions}/GlobalHooks.java (96%) rename {cms-extensions/src/main/java/com/condation/cms/extensions/hooks => cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions}/HttpHandlerWrapper.java (96%) rename {cms-extensions/src/main/java/com/condation/cms/extensions/hooks => cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions}/QueryOperationsWrapper.java (95%) rename {cms-extensions/src/main/java/com/condation/cms/extensions/hooks => cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions}/ServerHooks.java (97%) rename {cms-extensions/src/main/java/com/condation/cms/extensions/hooks => cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions}/TagsWrapper.java (97%) rename {cms-extensions/src/main/java/com/condation/cms/extensions/hooks => cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions}/TemplateComponentsWrapper.java (97%) rename {cms-extensions/src/main/java/com/condation/cms/extensions/hooks => cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions}/TemplateFunctionWrapper.java (97%) rename {cms-extensions/src/main/java/com/condation/cms/extensions/hooks => cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions}/TemplateHooks.java (97%) rename {cms-extensions/src/main/java/com/condation/cms/extensions/hooks => cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions}/TemplateSupplierWrapper.java (96%) rename cms-api/src/test/java/com/condation/cms/api/hooks/HookSystemTest.java => cms-hooksystem/src/test/java/com/condation/cms/hooksystem/CMSHookSystemTest.java (94%) rename {cms-extensions/src/test/java/com/condation/cms => cms-hooksystem/src/test/java/com/condation/cms/hooksystem}/extensions/ExtensionManagerTest.java (92%) rename {cms-extensions/src/test/java/com/condation/cms => cms-hooksystem/src/test/java/com/condation/cms/hooksystem}/extensions/GlobalExtensionsTest.java (93%) rename {cms-extensions => cms-hooksystem}/src/test/resources/site/extensions/hook-test.js (100%) delete mode 100644 test-server/modules/example-module/libs/example-module-8.0.0.jar create mode 100644 test-server/modules/example-module/libs/example-module-8.1.0.jar diff --git a/cms-api/src/main/java/com/condation/cms/api/hooks/HookSystem.java b/cms-api/src/main/java/com/condation/cms/api/hooks/HookSystem.java index efc7a4e3..e9c65dc7 100644 --- a/cms-api/src/main/java/com/condation/cms/api/hooks/HookSystem.java +++ b/cms-api/src/main/java/com/condation/cms/api/hooks/HookSystem.java @@ -20,14 +20,6 @@ * along with this program. If not, see . * #L% */ -import com.condation.cms.api.annotations.Filter; -import com.condation.cms.api.annotations.Action; -import com.condation.cms.api.utils.AnnotationsUtil; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; import java.util.Map; import lombok.extern.slf4j.Slf4j; @@ -37,78 +29,21 @@ * * @author t.marx */ -@Slf4j -public class HookSystem { +public interface HookSystem { - Multimap actions = ArrayListMultimap.create(); + void register(Object sourceObject); - Multimap filters = ArrayListMultimap.create(); + public void registerAction(final String name, final ActionFunction hookFunction); - public HookSystem () { - - } - public HookSystem(HookSystem source) { - this.actions.putAll(source.actions); - this.filters.putAll(source.filters); - } + public void registerAction(final String name, final ActionFunction hookFunction, int priority); - public void register(Object sourceObject) { - // Action-Methoden registrieren - List> actionMethods - = AnnotationsUtil.process(sourceObject, Action.class, List.of(ActionContext.class), Void.class); + public void registerFilter(final String name, final FilterFunction hookFunction); - for (AnnotationsUtil.CMSAnnotation ann : actionMethods) { - Action annotation = ann.annotation(); - registerAction(annotation.value(), context -> ann.invoke(context), annotation.priority()); - } + public void registerFilter(final String name, final FilterFunction hookFunction, int priority); - // Filter-Methoden registrieren - List> filterMethods - = AnnotationsUtil.process(sourceObject, Filter.class, List.of(FilterContext.class), Object.class); + public ActionContext doAction(final String name); - for (AnnotationsUtil.CMSAnnotation ann : filterMethods) { - Filter annotation = ann.annotation(); - registerFilter(annotation.value(), context -> ann.invoke(context), annotation.priority()); - } - } - - public void registerAction(final String name, final ActionFunction hookFunction) { - registerAction(name, hookFunction, 10); - } - - public void registerAction(final String name, final ActionFunction hookFunction, int priority) { - actions.put(name, new ActionHook<>(name, priority, hookFunction)); - } - - public void registerFilter(final String name, final FilterFunction hookFunction) { - registerFilter(name, hookFunction, 10); - } - - public void registerFilter(final String name, final FilterFunction hookFunction, int priority) { - filters.put(name, new FilterHook<>(name, priority, hookFunction)); - } - - public ActionContext doAction(final String name) { - return HookSystem.this.doAction(name, Map.of()); - } - - public ActionContext doAction(final String name, final Map arguments) { - var context = new ActionContext(new HashMap<>(arguments), new ArrayList<>()); - actions.get(name).stream() - .sorted((h1, h2) -> Integer.compare(h1.priority(), h2.priority())) - .map((action) -> { - try { - return action.function().apply(context); - } catch (Exception e) { - log.error("error executing action", e); - } - return null; - }) - .filter(value -> value != null) - .forEach(context.results()::add); - - return context; - } + public ActionContext doAction(final String name, final Map arguments); /** * calls all filters with the given parameters, if no filter is executed, @@ -119,22 +54,5 @@ public ActionContext doAction(final String name, final Map FilterContext doFilter(final String name, final T parameters) { - final FilterContext returnContext = new FilterContext( - parameters - ); - filters.get(name).stream() - .sorted((h1, h2) -> Integer.compare(h1.priority(), h2.priority())) - .forEach((var action) -> { - try { - var context = new FilterContext(returnContext.value()); - var result = action.function().apply(context); - returnContext.value((T) result); - } catch (Exception e) { - log.error("error on filter", e); - } - }); - - return returnContext; - } + public FilterContext doFilter(final String name, final T parameters); } diff --git a/cms-content/pom.xml b/cms-content/pom.xml index 774d2d16..787d2031 100644 --- a/cms-content/pom.xml +++ b/cms-content/pom.xml @@ -44,7 +44,7 @@ com.condation.cms - cms-core + cms-hooksystem org.projectlombok diff --git a/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java b/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java index 7b3899fd..c5644941 100644 --- a/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java +++ b/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java @@ -50,8 +50,8 @@ import com.condation.cms.content.views.model.View; import com.condation.cms.api.content.MapAccess; import com.condation.cms.content.pipeline.HTMLPipeline; -import com.condation.cms.extensions.hooks.DBHooks; -import com.condation.cms.extensions.hooks.TemplateHooks; +import com.condation.cms.hooksystem.extensions.DBHooks; +import com.condation.cms.hooksystem.extensions.TemplateHooks; import com.condation.cms.content.template.functions.LinkFunction; import com.condation.cms.content.template.functions.MarkdownFunction; import com.condation.cms.content.template.functions.list.NodeListFunctionBuilder; diff --git a/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipelineFactory.java b/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipelineFactory.java index 79cc5286..0357a07d 100644 --- a/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipelineFactory.java +++ b/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipelineFactory.java @@ -25,6 +25,7 @@ import com.condation.cms.api.hooks.HookSystem; import com.condation.cms.api.request.RequestContext; import com.condation.cms.api.template.TemplateEngine; +import com.condation.cms.hooksystem.CMSHookSystem; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -37,8 +38,8 @@ public final class ContentPipelineFactory { public static ContentPipeline create (final RequestContext requestContext, final TemplateEngine.Model model) { - var hookSystem = requestContext.get(HookSystemFeature.class).hookSystem(); - var pipeline = new ContentPipeline(new HookSystem(hookSystem), requestContext, model); + var hookSystem = (CMSHookSystem)requestContext.get(HookSystemFeature.class).hookSystem(); + var pipeline = new ContentPipeline(new CMSHookSystem(hookSystem), requestContext, model); pipeline.init(); return pipeline; diff --git a/cms-content/src/test/java/com/condation/cms/content/pipeline/HTMLPipelineTest.java b/cms-content/src/test/java/com/condation/cms/content/pipeline/HTMLPipelineTest.java index d23d0390..d831c188 100644 --- a/cms-content/src/test/java/com/condation/cms/content/pipeline/HTMLPipelineTest.java +++ b/cms-content/src/test/java/com/condation/cms/content/pipeline/HTMLPipelineTest.java @@ -22,6 +22,7 @@ */ import com.condation.cms.api.hooks.HookSystem; import com.condation.cms.api.hooks.Hooks; +import com.condation.cms.hooksystem.CMSHookSystem; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -34,7 +35,7 @@ class HTMLPipelineTest { @BeforeEach void setUp() { - hookSystem = new HookSystem(); + hookSystem = new CMSHookSystem(); pipeline = new HTMLPipeline(hookSystem); } diff --git a/cms-content/src/test/java/com/condation/cms/content/template/navigation/NavigationFunctionITest.java b/cms-content/src/test/java/com/condation/cms/content/template/navigation/NavigationFunctionITest.java index d3817c0e..9d20f325 100644 --- a/cms-content/src/test/java/com/condation/cms/content/template/navigation/NavigationFunctionITest.java +++ b/cms-content/src/test/java/com/condation/cms/content/template/navigation/NavigationFunctionITest.java @@ -42,6 +42,7 @@ import com.condation.cms.content.template.functions.navigation.NavigationFunction; import com.condation.cms.core.eventbus.DefaultEventBus; import com.condation.cms.filesystem.FileDB; +import com.condation.cms.hooksystem.CMSHookSystem; import com.google.inject.Injector; import java.nio.file.Path; import org.assertj.core.api.Assertions; @@ -110,7 +111,7 @@ public void setupRequestContext() { requestContext.add(ContentParserFeature.class, new ContentParserFeature(contentParser)); requestContext.add(ContentNodeMapperFeature.class, new ContentNodeMapperFeature(new ContentNodeMapper(db, contentParser))); requestContext.add(MarkdownRendererFeature.class, new MarkdownRendererFeature(new CMSMarkdownRenderer())); - requestContext.add(HookSystemFeature.class, new HookSystemFeature(new HookSystem())); + requestContext.add(HookSystemFeature.class, new HookSystemFeature(new CMSHookSystem())); Mockito.lenient().when(request.getAttribute("_requestContext")).thenReturn(requestContext); } diff --git a/cms-content/src/test/java/com/condation/cms/content/template/navigation/NavigationFunctionTest.java b/cms-content/src/test/java/com/condation/cms/content/template/navigation/NavigationFunctionTest.java index 4ad7edff..85a8740f 100644 --- a/cms-content/src/test/java/com/condation/cms/content/template/navigation/NavigationFunctionTest.java +++ b/cms-content/src/test/java/com/condation/cms/content/template/navigation/NavigationFunctionTest.java @@ -39,6 +39,7 @@ import com.condation.cms.api.markdown.MarkdownRenderer; import com.condation.cms.api.model.NavNode; import com.condation.cms.api.request.RequestContext; +import com.condation.cms.hooksystem.CMSHookSystem; import java.nio.file.Path; import java.util.List; import java.util.Optional; @@ -92,7 +93,7 @@ public void setup () { Mockito.lenient().when(content.byUri("current")).thenReturn(Optional.empty()); - hookSystem = new HookSystem(); + hookSystem = new CMSHookSystem(); requestContext = new RequestContext(); requestContext.add(HookSystemFeature.class, new HookSystemFeature(hookSystem)); requestContext.add(ContentParserFeature.class, new ContentParserFeature(contentParser)); diff --git a/cms-extensions/pom.xml b/cms-extensions/pom.xml index 8d289695..2ce7b7d3 100644 --- a/cms-extensions/pom.xml +++ b/cms-extensions/pom.xml @@ -24,7 +24,6 @@ com.condation.cms cms-filesystem - org.projectlombok lombok diff --git a/cms-hooksystem/pom.xml b/cms-hooksystem/pom.xml new file mode 100644 index 00000000..a41d646d --- /dev/null +++ b/cms-hooksystem/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + com.condation.cms + cms-parent + 8.1.0 + + cms-hooksystem + jar + CMS HookSystem + + + + com.condation.cms + cms-api + + + com.condation.cms + cms-extensions + + + + org.projectlombok + lombok + provided + + + \ No newline at end of file diff --git a/cms-api/src/main/java/com/condation/cms/api/hooks/ActionHook.java b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/ActionHook.java similarity index 90% rename from cms-api/src/main/java/com/condation/cms/api/hooks/ActionHook.java rename to cms-hooksystem/src/main/java/com/condation/cms/hooksystem/ActionHook.java index 3d02dac9..9c41cc31 100644 --- a/cms-api/src/main/java/com/condation/cms/api/hooks/ActionHook.java +++ b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/ActionHook.java @@ -1,4 +1,6 @@ -package com.condation.cms.api.hooks; +package com.condation.cms.hooksystem; + +import com.condation.cms.api.hooks.ActionFunction; /*- * #%L diff --git a/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/CMSHookSystem.java b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/CMSHookSystem.java new file mode 100644 index 00000000..9ca00428 --- /dev/null +++ b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/CMSHookSystem.java @@ -0,0 +1,145 @@ +package com.condation.cms.hooksystem; + +/*- + * #%L + * CMS Api + * %% + * Copyright (C) 2023 - 2026 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * #L% + */ +import com.condation.cms.api.annotations.Filter; +import com.condation.cms.api.annotations.Action; +import com.condation.cms.api.hooks.ActionContext; +import com.condation.cms.api.hooks.ActionFunction; +import com.condation.cms.api.hooks.FilterContext; +import com.condation.cms.api.hooks.FilterFunction; +import com.condation.cms.api.hooks.HookSystem; +import com.condation.cms.api.utils.AnnotationsUtil; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; + +/** + * + * Request based hook system. + * + * @author t.marx + */ +@Slf4j +public class CMSHookSystem implements HookSystem { + + Multimap actions = ArrayListMultimap.create(); + + Multimap filters = ArrayListMultimap.create(); + + public CMSHookSystem () { + + } + public CMSHookSystem(CMSHookSystem source) { + this.actions.putAll(source.actions); + this.filters.putAll(source.filters); + } + + public void register(Object sourceObject) { + // Action-Methoden registrieren + List> actionMethods + = AnnotationsUtil.process(sourceObject, Action.class, List.of(ActionContext.class), Void.class); + + for (AnnotationsUtil.CMSAnnotation ann : actionMethods) { + Action annotation = ann.annotation(); + registerAction(annotation.value(), context -> ann.invoke(context), annotation.priority()); + } + + // Filter-Methoden registrieren + List> filterMethods + = AnnotationsUtil.process(sourceObject, Filter.class, List.of(FilterContext.class), Object.class); + + for (AnnotationsUtil.CMSAnnotation ann : filterMethods) { + Filter annotation = ann.annotation(); + registerFilter(annotation.value(), context -> ann.invoke(context), annotation.priority()); + } + } + + public void registerAction(final String name, final ActionFunction hookFunction) { + registerAction(name, hookFunction, 10); + } + + public void registerAction(final String name, final ActionFunction hookFunction, int priority) { + actions.put(name, new ActionHook<>(name, priority, hookFunction)); + } + + public void registerFilter(final String name, final FilterFunction hookFunction) { + registerFilter(name, hookFunction, 10); + } + + public void registerFilter(final String name, final FilterFunction hookFunction, int priority) { + filters.put(name, new FilterHook<>(name, priority, hookFunction)); + } + + public ActionContext doAction(final String name) { + return doAction(name, Map.of()); + } + + public ActionContext doAction(final String name, final Map arguments) { + var context = new ActionContext(new HashMap<>(arguments), new ArrayList<>()); + actions.get(name).stream() + .sorted((h1, h2) -> Integer.compare(h1.priority(), h2.priority())) + .map((action) -> { + try { + return action.function().apply(context); + } catch (Exception e) { + log.error("error executing action", e); + } + return null; + }) + .filter(value -> value != null) + .forEach(context.results()::add); + + return context; + } + + /** + * calls all filters with the given parameters, if no filter is executed, + * the original parameters are returned + * + * @param + * @param name + * @param parameters + * @return + */ + public FilterContext doFilter(final String name, final T parameters) { + final FilterContext returnContext = new FilterContext( + parameters + ); + filters.get(name).stream() + .sorted((h1, h2) -> Integer.compare(h1.priority(), h2.priority())) + .forEach((var action) -> { + try { + var context = new FilterContext(returnContext.value()); + var result = action.function().apply(context); + returnContext.value((T) result); + } catch (Exception e) { + log.error("error on filter", e); + } + }); + + return returnContext; + } +} diff --git a/cms-api/src/main/java/com/condation/cms/api/hooks/FilterHook.java b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/FilterHook.java similarity index 90% rename from cms-api/src/main/java/com/condation/cms/api/hooks/FilterHook.java rename to cms-hooksystem/src/main/java/com/condation/cms/hooksystem/FilterHook.java index 1efd7714..32a4d503 100644 --- a/cms-api/src/main/java/com/condation/cms/api/hooks/FilterHook.java +++ b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/FilterHook.java @@ -1,4 +1,6 @@ -package com.condation.cms.api.hooks; +package com.condation.cms.hooksystem; + +import com.condation.cms.api.hooks.FilterFunction; /*- * #%L diff --git a/cms-api/src/main/java/com/condation/cms/api/hooks/Hook.java b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/Hook.java similarity index 95% rename from cms-api/src/main/java/com/condation/cms/api/hooks/Hook.java rename to cms-hooksystem/src/main/java/com/condation/cms/hooksystem/Hook.java index 79a6c826..185b8106 100644 --- a/cms-api/src/main/java/com/condation/cms/api/hooks/Hook.java +++ b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/Hook.java @@ -1,4 +1,4 @@ -package com.condation.cms.api.hooks; +package com.condation.cms.hooksystem; /*- * #%L diff --git a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/ContentHooks.java b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/ContentHooks.java similarity index 97% rename from cms-extensions/src/main/java/com/condation/cms/extensions/hooks/ContentHooks.java rename to cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/ContentHooks.java index fbdf2453..94fe9b80 100644 --- a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/ContentHooks.java +++ b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/ContentHooks.java @@ -1,4 +1,4 @@ -package com.condation.cms.extensions.hooks; +package com.condation.cms.hooksystem.extensions; /*- * #%L diff --git a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/DBHooks.java b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/DBHooks.java similarity index 97% rename from cms-extensions/src/main/java/com/condation/cms/extensions/hooks/DBHooks.java rename to cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/DBHooks.java index 2b3e331e..f00e34d4 100644 --- a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/DBHooks.java +++ b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/DBHooks.java @@ -1,4 +1,4 @@ -package com.condation.cms.extensions.hooks; +package com.condation.cms.hooksystem.extensions; /*- * #%L diff --git a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/GlobalHooks.java b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/GlobalHooks.java similarity index 96% rename from cms-extensions/src/main/java/com/condation/cms/extensions/hooks/GlobalHooks.java rename to cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/GlobalHooks.java index 5258c05b..fca4af0b 100644 --- a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/GlobalHooks.java +++ b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/GlobalHooks.java @@ -1,4 +1,4 @@ -package com.condation.cms.extensions.hooks; +package com.condation.cms.hooksystem.extensions; /*- * #%L diff --git a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/HttpHandlerWrapper.java b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/HttpHandlerWrapper.java similarity index 96% rename from cms-extensions/src/main/java/com/condation/cms/extensions/hooks/HttpHandlerWrapper.java rename to cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/HttpHandlerWrapper.java index ea7a0541..3978f74a 100644 --- a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/HttpHandlerWrapper.java +++ b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/HttpHandlerWrapper.java @@ -1,4 +1,4 @@ -package com.condation.cms.extensions.hooks; +package com.condation.cms.hooksystem.extensions; /*- * #%L diff --git a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/QueryOperationsWrapper.java b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/QueryOperationsWrapper.java similarity index 95% rename from cms-extensions/src/main/java/com/condation/cms/extensions/hooks/QueryOperationsWrapper.java rename to cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/QueryOperationsWrapper.java index 20822e5c..b9012c8e 100644 --- a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/QueryOperationsWrapper.java +++ b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/QueryOperationsWrapper.java @@ -1,4 +1,4 @@ -package com.condation.cms.extensions.hooks; +package com.condation.cms.hooksystem.extensions; /*- * #%L diff --git a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/ServerHooks.java b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/ServerHooks.java similarity index 97% rename from cms-extensions/src/main/java/com/condation/cms/extensions/hooks/ServerHooks.java rename to cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/ServerHooks.java index 4094809b..408248d0 100644 --- a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/ServerHooks.java +++ b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/ServerHooks.java @@ -1,4 +1,4 @@ -package com.condation.cms.extensions.hooks; +package com.condation.cms.hooksystem.extensions; /*- * #%L diff --git a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TagsWrapper.java b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/TagsWrapper.java similarity index 97% rename from cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TagsWrapper.java rename to cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/TagsWrapper.java index 31b9d11c..9b96dd70 100644 --- a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TagsWrapper.java +++ b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/TagsWrapper.java @@ -1,4 +1,4 @@ -package com.condation.cms.extensions.hooks; +package com.condation.cms.hooksystem.extensions; /*- * #%L diff --git a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TemplateComponentsWrapper.java b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/TemplateComponentsWrapper.java similarity index 97% rename from cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TemplateComponentsWrapper.java rename to cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/TemplateComponentsWrapper.java index 3a8a1014..439a3678 100644 --- a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TemplateComponentsWrapper.java +++ b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/TemplateComponentsWrapper.java @@ -1,4 +1,4 @@ -package com.condation.cms.extensions.hooks; +package com.condation.cms.hooksystem.extensions; /*- * #%L diff --git a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TemplateFunctionWrapper.java b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/TemplateFunctionWrapper.java similarity index 97% rename from cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TemplateFunctionWrapper.java rename to cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/TemplateFunctionWrapper.java index 9bd10fa6..c5b3c447 100644 --- a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TemplateFunctionWrapper.java +++ b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/TemplateFunctionWrapper.java @@ -1,4 +1,4 @@ -package com.condation.cms.extensions.hooks; +package com.condation.cms.hooksystem.extensions; /*- * #%L diff --git a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TemplateHooks.java b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/TemplateHooks.java similarity index 97% rename from cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TemplateHooks.java rename to cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/TemplateHooks.java index 946997a9..fdd461aa 100644 --- a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TemplateHooks.java +++ b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/TemplateHooks.java @@ -1,4 +1,4 @@ -package com.condation.cms.extensions.hooks; +package com.condation.cms.hooksystem.extensions; /*- * #%L diff --git a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TemplateSupplierWrapper.java b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/TemplateSupplierWrapper.java similarity index 96% rename from cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TemplateSupplierWrapper.java rename to cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/TemplateSupplierWrapper.java index 007566de..004e92c1 100644 --- a/cms-extensions/src/main/java/com/condation/cms/extensions/hooks/TemplateSupplierWrapper.java +++ b/cms-hooksystem/src/main/java/com/condation/cms/hooksystem/extensions/TemplateSupplierWrapper.java @@ -1,4 +1,4 @@ -package com.condation.cms.extensions.hooks; +package com.condation.cms.hooksystem.extensions; /*- * #%L diff --git a/cms-api/src/test/java/com/condation/cms/api/hooks/HookSystemTest.java b/cms-hooksystem/src/test/java/com/condation/cms/hooksystem/CMSHookSystemTest.java similarity index 94% rename from cms-api/src/test/java/com/condation/cms/api/hooks/HookSystemTest.java rename to cms-hooksystem/src/test/java/com/condation/cms/hooksystem/CMSHookSystemTest.java index 5b46ed5d..a6b608d7 100644 --- a/cms-api/src/test/java/com/condation/cms/api/hooks/HookSystemTest.java +++ b/cms-hooksystem/src/test/java/com/condation/cms/hooksystem/CMSHookSystemTest.java @@ -1,4 +1,4 @@ -package com.condation.cms.api.hooks; +package com.condation.cms.hooksystem; /*- * #%L @@ -24,6 +24,8 @@ import com.condation.cms.api.annotations.Filter; import com.condation.cms.api.annotations.Action; +import com.condation.cms.api.hooks.ActionContext; +import com.condation.cms.api.hooks.FilterContext; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -35,13 +37,13 @@ * * @author t.marx */ -public class HookSystemTest { +public class CMSHookSystemTest { - private HookSystem hookSystem; + private CMSHookSystem hookSystem; @BeforeEach public void setup() { - hookSystem = new HookSystem(); + hookSystem = new CMSHookSystem(); } @Test diff --git a/cms-extensions/src/test/java/com/condation/cms/extensions/ExtensionManagerTest.java b/cms-hooksystem/src/test/java/com/condation/cms/hooksystem/extensions/ExtensionManagerTest.java similarity index 92% rename from cms-extensions/src/test/java/com/condation/cms/extensions/ExtensionManagerTest.java rename to cms-hooksystem/src/test/java/com/condation/cms/hooksystem/extensions/ExtensionManagerTest.java index 14a11bf4..f512d8af 100644 --- a/cms-extensions/src/test/java/com/condation/cms/extensions/ExtensionManagerTest.java +++ b/cms-hooksystem/src/test/java/com/condation/cms/hooksystem/extensions/ExtensionManagerTest.java @@ -1,4 +1,4 @@ -package com.condation.cms.extensions; +package com.condation.cms.hooksystem.extensions; /*- * #%L @@ -28,7 +28,9 @@ import com.condation.cms.api.hooks.HookSystem; import com.condation.cms.api.request.RequestContext; import com.condation.cms.api.theme.Theme; +import com.condation.cms.extensions.ExtensionManager; import com.condation.cms.filesystem.FileSystem; +import com.condation.cms.hooksystem.CMSHookSystem; import java.io.IOException; import java.nio.file.Path; import org.assertj.core.api.Assertions; @@ -91,7 +93,7 @@ public void setup() throws Exception { public void test_with_auth() throws IOException { var requestContext = new RequestContext(); - final HookSystem hookSystem = new HookSystem(); + final HookSystem hookSystem = new CMSHookSystem(); requestContext.add(HookSystemFeature.class, new HookSystemFeature(hookSystem)); requestContext.add(AuthFeature.class, new AuthFeature("thorsten")); extensionManager.newContext(theme, requestContext); @@ -105,7 +107,7 @@ public void test_with_auth() throws IOException { public void test_without_auth() throws IOException { var requestContext = new RequestContext(); - final HookSystem hookSystem = new HookSystem(); + final HookSystem hookSystem = new CMSHookSystem(); requestContext.add(HookSystemFeature.class, new HookSystemFeature(hookSystem)); extensionManager.newContext(theme, requestContext); diff --git a/cms-extensions/src/test/java/com/condation/cms/extensions/GlobalExtensionsTest.java b/cms-hooksystem/src/test/java/com/condation/cms/hooksystem/extensions/GlobalExtensionsTest.java similarity index 93% rename from cms-extensions/src/test/java/com/condation/cms/extensions/GlobalExtensionsTest.java rename to cms-hooksystem/src/test/java/com/condation/cms/hooksystem/extensions/GlobalExtensionsTest.java index c1521243..e1137881 100644 --- a/cms-extensions/src/test/java/com/condation/cms/extensions/GlobalExtensionsTest.java +++ b/cms-hooksystem/src/test/java/com/condation/cms/hooksystem/extensions/GlobalExtensionsTest.java @@ -1,4 +1,4 @@ -package com.condation.cms.extensions; +package com.condation.cms.hooksystem.extensions; /*- * #%L @@ -22,8 +22,9 @@ */ -import com.condation.cms.extensions.GlobalExtensions; import com.condation.cms.api.hooks.HookSystem; +import com.condation.cms.extensions.GlobalExtensions; +import com.condation.cms.hooksystem.CMSHookSystem; import java.io.IOException; import org.assertj.core.api.Assertions; import org.graalvm.polyglot.Context; @@ -51,7 +52,7 @@ public static void initEngine() throws IOException { .allowValueSharing(true) .build(); - hookSystem = new HookSystem(); + hookSystem = new CMSHookSystem(); globalExtensions = new GlobalExtensions(hookSystem, context); globalExtensions.init(); diff --git a/cms-extensions/src/test/resources/site/extensions/hook-test.js b/cms-hooksystem/src/test/resources/site/extensions/hook-test.js similarity index 100% rename from cms-extensions/src/test/resources/site/extensions/hook-test.js rename to cms-hooksystem/src/test/resources/site/extensions/hook-test.js diff --git a/cms-server/pom.xml b/cms-server/pom.xml index e7251733..83f12a01 100644 --- a/cms-server/pom.xml +++ b/cms-server/pom.xml @@ -44,6 +44,10 @@ com.condation.cms + cms-hooksystem + + + com.condation.cms cms-auth diff --git a/cms-server/src/main/java/com/condation/cms/request/RequestContextFactory.java b/cms-server/src/main/java/com/condation/cms/request/RequestContextFactory.java index 2700159e..f2959f54 100644 --- a/cms-server/src/main/java/com/condation/cms/request/RequestContextFactory.java +++ b/cms-server/src/main/java/com/condation/cms/request/RequestContextFactory.java @@ -35,7 +35,6 @@ import com.condation.cms.api.feature.features.InjectorFeature; import com.condation.cms.api.feature.features.IsDevModeFeature; import com.condation.cms.api.feature.features.MarkdownRendererFeature; -import com.condation.cms.api.feature.features.ModuleManagerFeature; import com.condation.cms.api.feature.features.RequestFeature; import com.condation.cms.api.feature.features.ServerPropertiesFeature; import com.condation.cms.api.feature.features.SiteMediaServiceFeature; @@ -56,11 +55,11 @@ import com.condation.cms.content.tags.Tags; import com.condation.cms.content.tags.TagParser; import com.condation.cms.extensions.ExtensionManager; -import com.condation.cms.extensions.hooks.ContentHooks; -import com.condation.cms.extensions.hooks.DBHooks; -import com.condation.cms.extensions.hooks.ServerHooks; -import com.condation.cms.extensions.hooks.TemplateHooks; import com.condation.cms.extensions.request.RequestExtensions; +import com.condation.cms.hooksystem.extensions.ContentHooks; +import com.condation.cms.hooksystem.extensions.DBHooks; +import com.condation.cms.hooksystem.extensions.ServerHooks; +import com.condation.cms.hooksystem.extensions.TemplateHooks; import com.condation.modules.api.ModuleManager; import com.google.inject.Injector; import java.io.IOException; diff --git a/cms-server/src/main/java/com/condation/cms/server/configs/ServerGlobalModule.java b/cms-server/src/main/java/com/condation/cms/server/configs/ServerGlobalModule.java index aaaa2051..aacd4c81 100644 --- a/cms-server/src/main/java/com/condation/cms/server/configs/ServerGlobalModule.java +++ b/cms-server/src/main/java/com/condation/cms/server/configs/ServerGlobalModule.java @@ -38,6 +38,7 @@ import com.condation.cms.core.messaging.DefaultMessaging; import com.condation.cms.core.scheduler.ServerCronJobScheduler; import com.condation.cms.core.site.DefaultSiteService; +import com.condation.cms.hooksystem.CMSHookSystem; import com.condation.modules.api.ModuleManager; import com.condation.modules.manager.ModuleAPIClassLoader; import com.condation.modules.manager.ModuleManagerImpl; @@ -165,7 +166,7 @@ public SiteService siteService() { @Singleton @Named("server") public HookSystem hookSystem() { - return new HookSystem(); + return new CMSHookSystem(); } @Provides diff --git a/cms-server/src/main/java/com/condation/cms/server/configs/SiteGlobalModule.java b/cms-server/src/main/java/com/condation/cms/server/configs/SiteGlobalModule.java index 53a543ee..402b6699 100644 --- a/cms-server/src/main/java/com/condation/cms/server/configs/SiteGlobalModule.java +++ b/cms-server/src/main/java/com/condation/cms/server/configs/SiteGlobalModule.java @@ -33,7 +33,8 @@ import com.condation.cms.core.scheduler.SingleCronJobScheduler; import com.condation.cms.core.scheduler.SiteCronJobScheduler; import com.condation.cms.extensions.GlobalExtensions; -import com.condation.cms.extensions.hooks.GlobalHooks; +import com.condation.cms.hooksystem.CMSHookSystem; +import com.condation.cms.hooksystem.extensions.GlobalHooks; import com.condation.modules.api.ModuleManager; import com.google.inject.Binder; import com.google.inject.Injector; @@ -75,7 +76,7 @@ public Context context(Engine engine) throws IOException { @Singleton @Named("global") public HookSystem hookSystem () { - return new HookSystem(); + return new CMSHookSystem(); } @Provides diff --git a/cms-server/src/main/java/com/condation/cms/server/configs/SiteModulesModule.java b/cms-server/src/main/java/com/condation/cms/server/configs/SiteModulesModule.java index 758923ad..5094d5f2 100644 --- a/cms-server/src/main/java/com/condation/cms/server/configs/SiteModulesModule.java +++ b/cms-server/src/main/java/com/condation/cms/server/configs/SiteModulesModule.java @@ -30,6 +30,7 @@ import com.condation.cms.api.template.TemplateEngine; import com.condation.cms.api.theme.Theme; import com.condation.cms.filesystem.FileDB; +import com.condation.cms.hooksystem.CMSHookSystem; import com.condation.modules.api.ModuleManager; import com.condation.modules.manager.ModuleAPIClassLoader; import com.condation.modules.manager.ModuleManagerImpl; @@ -156,7 +157,7 @@ public TemplateEngine resolveTemplateEngine(SiteProperties siteProperties, Theme */ @Provides public HookSystem hookSystem(final ModuleManager moduleManager) { - var hookSystem = new HookSystem(); + var hookSystem = new CMSHookSystem(); /* moduleManager.extensions(HookSystemRegisterExtensionPoint.class).forEach(extensionPoint -> { diff --git a/cms-server/src/main/java/com/condation/cms/server/handler/extensions/JettyHttpHandlerExtensionHandler.java b/cms-server/src/main/java/com/condation/cms/server/handler/extensions/JettyHttpHandlerExtensionHandler.java index f933a914..655ffd7c 100644 --- a/cms-server/src/main/java/com/condation/cms/server/handler/extensions/JettyHttpHandlerExtensionHandler.java +++ b/cms-server/src/main/java/com/condation/cms/server/handler/extensions/JettyHttpHandlerExtensionHandler.java @@ -26,9 +26,8 @@ import com.condation.cms.api.request.RequestContext; import com.condation.cms.api.utils.RequestUtil; import com.condation.cms.extensions.HttpHandlerExtension; -import com.condation.cms.extensions.hooks.ServerHooks; import com.condation.cms.extensions.http.JettyHttpHandlerWrapper; -import com.condation.cms.server.filter.CreateRequestContextFilter; +import com.condation.cms.hooksystem.extensions.ServerHooks; import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/cms-server/src/main/java/com/condation/cms/server/handler/http/APIHandler.java b/cms-server/src/main/java/com/condation/cms/server/handler/http/APIHandler.java index ff45555d..0007b46b 100644 --- a/cms-server/src/main/java/com/condation/cms/server/handler/http/APIHandler.java +++ b/cms-server/src/main/java/com/condation/cms/server/handler/http/APIHandler.java @@ -30,9 +30,8 @@ import com.condation.cms.api.request.RequestContext; import com.condation.cms.api.utils.RequestUtil; import com.condation.cms.extensions.HttpHandlerExtension; -import com.condation.cms.extensions.hooks.ServerHooks; import com.condation.cms.extensions.http.JettyHttpHandlerWrapper; -import com.condation.cms.server.filter.CreateRequestContextFilter; +import com.condation.cms.hooksystem.extensions.ServerHooks; import com.condation.cms.server.handler.AbstractHandler; import com.condation.modules.api.ModuleManager; import com.google.inject.Inject; @@ -40,7 +39,6 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.Callback; diff --git a/cms-server/src/main/java/com/condation/cms/server/handler/http/RoutesHandler.java b/cms-server/src/main/java/com/condation/cms/server/handler/http/RoutesHandler.java index ab3b390c..706f77b4 100644 --- a/cms-server/src/main/java/com/condation/cms/server/handler/http/RoutesHandler.java +++ b/cms-server/src/main/java/com/condation/cms/server/handler/http/RoutesHandler.java @@ -29,12 +29,10 @@ import com.condation.cms.api.request.RequestContext; import com.condation.cms.api.utils.RequestUtil; import com.condation.cms.extensions.HttpHandlerExtension; -import com.condation.cms.extensions.hooks.ServerHooks; import com.condation.cms.extensions.http.JettyHttpHandlerWrapper; -import com.condation.cms.server.filter.CreateRequestContextFilter; +import com.condation.cms.hooksystem.extensions.ServerHooks; import com.condation.modules.api.ModuleManager; import com.google.inject.Inject; -import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/cms-server/src/main/java/com/condation/cms/server/host/VHost.java b/cms-server/src/main/java/com/condation/cms/server/host/VHost.java index 0229c5d4..aaa53d32 100644 --- a/cms-server/src/main/java/com/condation/cms/server/host/VHost.java +++ b/cms-server/src/main/java/com/condation/cms/server/host/VHost.java @@ -53,8 +53,8 @@ import com.condation.cms.core.utils.SiteUtil; import com.condation.cms.core.configuration.ConfigManagement; import com.condation.cms.extensions.GlobalExtensions; -import com.condation.cms.extensions.hooks.GlobalHooks; import com.condation.cms.filesystem.FileDB; +import com.condation.cms.hooksystem.extensions.GlobalHooks; import com.condation.cms.media.MediaManager; import com.condation.cms.media.SiteMediaManager; import com.condation.cms.media.ThemeMediaManager; diff --git a/cms-templates/pom.xml b/cms-templates/pom.xml index 11e7809e..a676e34b 100644 --- a/cms-templates/pom.xml +++ b/cms-templates/pom.xml @@ -35,6 +35,11 @@ cms-core + + com.condation.cms + cms-hooksystem + + org.apache.commons commons-jexl3 diff --git a/cms-templates/src/main/java/com/condation/cms/templates/DynamicConfiguration.java b/cms-templates/src/main/java/com/condation/cms/templates/DynamicConfiguration.java index 5d51aaa6..0fa6af6e 100644 --- a/cms-templates/src/main/java/com/condation/cms/templates/DynamicConfiguration.java +++ b/cms-templates/src/main/java/com/condation/cms/templates/DynamicConfiguration.java @@ -24,7 +24,7 @@ import com.condation.cms.api.feature.features.InjectorFeature; import com.condation.cms.api.model.Parameter; import com.condation.cms.api.request.RequestContext; -import com.condation.cms.extensions.hooks.TemplateHooks; +import com.condation.cms.hooksystem.extensions.TemplateHooks; import com.condation.cms.templates.components.TemplateComponents; import com.condation.cms.templates.components.TemplateFunctions; import com.condation.cms.templates.functions.TemplateFunction; diff --git a/cms-templates/src/main/java/com/condation/cms/templates/module/CMSModuleTemplateEngine.java b/cms-templates/src/main/java/com/condation/cms/templates/module/CMSModuleTemplateEngine.java index c07d8899..47c8e497 100644 --- a/cms-templates/src/main/java/com/condation/cms/templates/module/CMSModuleTemplateEngine.java +++ b/cms-templates/src/main/java/com/condation/cms/templates/module/CMSModuleTemplateEngine.java @@ -30,7 +30,7 @@ import com.condation.cms.api.request.RequestContext; import com.condation.cms.api.template.TemplateEngine; import com.condation.cms.api.theme.Theme; -import com.condation.cms.extensions.hooks.TemplateHooks; +import com.condation.cms.hooksystem.extensions.TemplateHooks; import com.condation.cms.templates.CMSTemplateEngine; import com.condation.cms.templates.DynamicConfiguration; import com.condation.cms.templates.TemplateEngineFactory; diff --git a/cms-templates/src/test/java/com/condation/cms/templates/TemplateEngineFunctionsTest.java b/cms-templates/src/test/java/com/condation/cms/templates/TemplateEngineFunctionsTest.java index 99220ccd..3a429563 100644 --- a/cms-templates/src/test/java/com/condation/cms/templates/TemplateEngineFunctionsTest.java +++ b/cms-templates/src/test/java/com/condation/cms/templates/TemplateEngineFunctionsTest.java @@ -29,7 +29,8 @@ import com.condation.cms.api.hooks.HookSystem; import com.condation.cms.api.model.Parameter; import com.condation.cms.api.request.RequestContext; -import com.condation.cms.extensions.hooks.TemplateHooks; +import com.condation.cms.hooksystem.CMSHookSystem; +import com.condation.cms.hooksystem.extensions.TemplateHooks; import com.condation.cms.templates.components.TemplateComponents; import com.condation.cms.templates.loaders.StringTemplateLoader; import com.condation.modules.api.ModuleManager; @@ -71,7 +72,7 @@ public void test_date() throws IOException { @Test void getFunctionsFromDynamicConig () { var requestContext = new RequestContext(); - requestContext.add(HookSystemFeature.class, new HookSystemFeature(new HookSystem())); + requestContext.add(HookSystemFeature.class, new HookSystemFeature(new CMSHookSystem())); requestContext.add(TemplateHooks.class, new TemplateHooks(requestContext)); var injectorMock = Mockito.mock(Injector.class); diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 05be68ee..7b63d240 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -45,6 +45,10 @@ com.condation.cms cms-content + + com.condation.cms + cms-hooksystem + com.condation.cms cms-media diff --git a/integration-tests/src/test/java/com/condation/cms/TestHelper.java b/integration-tests/src/test/java/com/condation/cms/TestHelper.java index 8d18e143..8637763d 100644 --- a/integration-tests/src/test/java/com/condation/cms/TestHelper.java +++ b/integration-tests/src/test/java/com/condation/cms/TestHelper.java @@ -45,11 +45,12 @@ import com.condation.cms.content.tags.TagParser; import com.condation.cms.core.configuration.ConfigurationFactory; import com.condation.cms.core.configuration.properties.ExtendedServerProperties; -import com.condation.cms.extensions.hooks.DBHooks; -import com.condation.cms.extensions.hooks.TemplateHooks; import com.condation.cms.extensions.request.RequestExtensions; import com.condation.cms.media.FileMediaService; import com.condation.cms.core.theme.DefaultTheme; +import com.condation.cms.hooksystem.CMSHookSystem; +import com.condation.cms.hooksystem.extensions.DBHooks; +import com.condation.cms.hooksystem.extensions.TemplateHooks; import com.condation.cms.test.TestSiteProperties; import com.google.inject.Injector; import java.io.IOException; @@ -83,7 +84,7 @@ public static RequestContext requestContext(String uri) throws IOException { context.add(SiteMediaServiceFeature.class, new SiteMediaServiceFeature(new FileMediaService(null))); context.add(InjectorFeature.class, new InjectorFeature(Mockito.mock(Injector.class))); - context.add(HookSystemFeature.class, new HookSystemFeature(new HookSystem())); + context.add(HookSystemFeature.class, new HookSystemFeature(new CMSHookSystem())); context.add(ContentNodeMapperFeature.class, new ContentNodeMapperFeature(null)); context.add(MarkdownRendererFeature.class, new MarkdownRendererFeature(null)); @@ -117,7 +118,7 @@ public static RequestContext requestContext(String uri, ContentParser contentPar context.add(ContentParserFeature.class, new ContentParserFeature(contentParser)); context.add(MarkdownRendererFeature.class, new MarkdownRendererFeature(markdownRenderer)); context.add(ContentNodeMapperFeature.class, new ContentNodeMapperFeature(contentMapper)); - context.add(HookSystemFeature.class, new HookSystemFeature(new HookSystem())); + context.add(HookSystemFeature.class, new HookSystemFeature(new CMSHookSystem())); context.add(SitePropertiesFeature.class, new SitePropertiesFeature(new TestSiteProperties(Map.of( "context_path", "/" )))); diff --git a/modules/ui-module/pom.xml b/modules/ui-module/pom.xml index 5a900dcb..263ff2bf 100644 --- a/modules/ui-module/pom.xml +++ b/modules/ui-module/pom.xml @@ -23,6 +23,10 @@ com.condation.cms cms-auth + + com.condation.cms + cms-hooksystem + io.jsonwebtoken jjwt-api diff --git a/modules/ui-module/src/test/java/com/condation/cms/modules/ui/utils/TemplateEngineTest.java b/modules/ui-module/src/test/java/com/condation/cms/modules/ui/utils/TemplateEngineTest.java index 1966f179..b9290b7f 100644 --- a/modules/ui-module/src/test/java/com/condation/cms/modules/ui/utils/TemplateEngineTest.java +++ b/modules/ui-module/src/test/java/com/condation/cms/modules/ui/utils/TemplateEngineTest.java @@ -31,6 +31,7 @@ import com.condation.cms.api.ui.elements.MenuEntry; import com.condation.cms.auth.services.User; import com.condation.cms.core.cache.LocalCacheProvider; +import com.condation.cms.hooksystem.CMSHookSystem; import com.condation.modules.api.ModuleManager; import java.util.ArrayList; import java.util.List; @@ -61,7 +62,7 @@ public void testSomeMethod() { TemplateEngine templateEngine = new TemplateEngine(cacheManager); - var hookSystem = new HookSystem(); + var hookSystem = new CMSHookSystem(); hookSystem.registerFilter("module/ui/menu", (FilterContext context) -> { var menu = context.value(); diff --git a/modules/ui-module/src/test/java/com/condation/cms/modules/ui/utils/UIHooksTest.java b/modules/ui-module/src/test/java/com/condation/cms/modules/ui/utils/UIHooksTest.java index 2771a844..b7121b8f 100644 --- a/modules/ui-module/src/test/java/com/condation/cms/modules/ui/utils/UIHooksTest.java +++ b/modules/ui-module/src/test/java/com/condation/cms/modules/ui/utils/UIHooksTest.java @@ -24,6 +24,7 @@ import com.condation.cms.api.hooks.HookSystem; import com.condation.cms.api.ui.elements.Menu; import com.condation.cms.api.ui.elements.MenuEntry; +import com.condation.cms.hooksystem.CMSHookSystem; import java.util.ArrayList; import java.util.List; import org.assertj.core.api.Assertions; @@ -38,7 +39,7 @@ public class UIHooksTest { @Test public void registerMenuEntry() { - HookSystem hookSystem = new HookSystem(); + HookSystem hookSystem = new CMSHookSystem(); hookSystem.registerFilter(UIHooks.HOOK_MENU, (FilterContext context) -> { var menu = context.value(); @@ -62,7 +63,7 @@ public void registerMenuEntry() { @Test public void registerMenuEntryAreSorted() { - HookSystem hookSystem = new HookSystem(); + HookSystem hookSystem = new CMSHookSystem(); hookSystem.registerFilter(UIHooks.HOOK_MENU, (FilterContext context) -> { var menu = context.value(); diff --git a/pom.xml b/pom.xml index 06516aa0..9c7985f2 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ integration-tests cms-core cms-test + cms-hooksystem 2023 @@ -107,6 +108,11 @@ com.condation.cms cms-test ${project.version} + + + com.condation.cms + cms-hooksystem + ${project.version} com.condation.modules.framework diff --git a/test-server/modules/example-module/libs/example-module-8.0.0.jar b/test-server/modules/example-module/libs/example-module-8.0.0.jar deleted file mode 100644 index 3ab59264ad739da2ea4d3fed51eaf1f693a4cf15..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19536 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 Date: Tue, 26 May 2026 16:58:50 +0200 Subject: [PATCH 4/4] add missing class