From 2c65e753b11e36a41edb8a5dc0378227f0f62aac Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Sun, 17 Nov 2024 19:58:27 +0100 Subject: [PATCH 01/11] introduce a http api endpoint for extensions and modules --- .../http/APIHandlerExtensionPoint.java | 29 ++++ .../cms/api/extensions/http/HttpHandler.java | 36 +++++ .../cms/api/extensions/http/PathMapping.java | 53 +++++++ .../com/condation/cms/api/hooks/Hooks.java | 1 + .../cms/extensions/hooks/ServerHooks.java | 8 ++ .../condation/cms/media/MediaServiceTest.java | 1 - .../java/com/condation/cms/server/VHost.java | 17 +++ .../cms/server/configs/SiteHandlerModule.java | 3 + .../cms/server/handler/api/APIHandler.java | 133 ++++++++++++++++++ .../cms/server/handler/api/APIRegistry.java | 41 ++++++ .../JettyExtensionRouteHandler.java | 1 - .../JettyHttpHandlerExtensionHandler.java | 3 +- .../example/ExampleApiHandlerExtension.java | 64 +++++++++ .../features/extensions/http.extension.js | 17 +++ .../libs/example-module-7.0.0.jar | Bin 16683 -> 0 bytes .../libs/example-module-7.2.0.jar | Bin 0 -> 18700 bytes .../modules/example-module/module.properties | 2 +- 17 files changed, 404 insertions(+), 5 deletions(-) create mode 100644 cms-api/src/main/java/com/condation/cms/api/extensions/http/APIHandlerExtensionPoint.java create mode 100644 cms-api/src/main/java/com/condation/cms/api/extensions/http/HttpHandler.java create mode 100644 cms-api/src/main/java/com/condation/cms/api/extensions/http/PathMapping.java create mode 100644 cms-server/src/main/java/com/condation/cms/server/handler/api/APIHandler.java create mode 100644 cms-server/src/main/java/com/condation/cms/server/handler/api/APIRegistry.java create mode 100644 modules/example-module/src/main/java/com/condation/cms/modules/example/ExampleApiHandlerExtension.java delete mode 100644 test-server/modules/example-module/libs/example-module-7.0.0.jar create mode 100644 test-server/modules/example-module/libs/example-module-7.2.0.jar diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/http/APIHandlerExtensionPoint.java b/cms-api/src/main/java/com/condation/cms/api/extensions/http/APIHandlerExtensionPoint.java new file mode 100644 index 000000000..4c94490a1 --- /dev/null +++ b/cms-api/src/main/java/com/condation/cms/api/extensions/http/APIHandlerExtensionPoint.java @@ -0,0 +1,29 @@ +package com.condation.cms.api.extensions.http; + +/*- + * #%L + * cms-api + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.api.extensions.AbstractExtensionPoint; + +public abstract class APIHandlerExtensionPoint extends AbstractExtensionPoint { + abstract public PathMapping getMapping(); +} diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/http/HttpHandler.java b/cms-api/src/main/java/com/condation/cms/api/extensions/http/HttpHandler.java new file mode 100644 index 000000000..8c495ed42 --- /dev/null +++ b/cms-api/src/main/java/com/condation/cms/api/extensions/http/HttpHandler.java @@ -0,0 +1,36 @@ +package com.condation.cms.api.extensions.http; + +/*- + * #%L + * cms-api + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public + * License along with this program. If not, see + * . + * #L% + */ + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; + +/** + * + * @author t.marx + */ +public interface HttpHandler { + + boolean handle (Request request, Response response, Callback callback) throws Exception; +} diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/http/PathMapping.java b/cms-api/src/main/java/com/condation/cms/api/extensions/http/PathMapping.java new file mode 100644 index 000000000..11f297d6c --- /dev/null +++ b/cms-api/src/main/java/com/condation/cms/api/extensions/http/PathMapping.java @@ -0,0 +1,53 @@ +package com.condation.cms.api.extensions.http; + +/*- + * #%L + * cms-api + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public + * License along with this program. If not, see + * . + * #L% + */ + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.eclipse.jetty.http.pathmap.PathSpec; + +public class PathMapping { + + private List mappings; + + public PathMapping () { + mappings = new ArrayList<>(); + } + + public void add (PathSpec pathSpec, String method, HttpHandler handler) { + mappings.add(new Mapping(pathSpec, method, handler)); + } + + public Optional getMatchingHandler (String uri, String method) { + return mappings.stream().filter(entry -> + entry.pathSpec.matches(uri) && entry.method.equalsIgnoreCase(method) + ).map(entry -> entry.handler).findFirst(); + } + + + private static record Mapping (PathSpec pathSpec, String method, HttpHandler handler){}; +} 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 325773f32..61a5ce042 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 @@ -38,6 +38,7 @@ public enum Hooks { SCHEDULER_REMOVE("system/scheduler/remove"), HTTP_EXTENSION("system/server/http/extension"), HTTP_ROUTE("system/server/http/route"), + API_ROUTE("system/server/api/route"), TEMPLATE_SUPPLIER("system/template/supplier"), TEMPLATE_FUNCTION("system/template/function") ; 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 4387f8f4b..f4663fb5a 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 @@ -59,4 +59,12 @@ public HttpHandlerWrapper getHttpRoutes () { return httpExtensions; } + + public HttpHandlerWrapper getAPIRoutes () { + var httpExtensions = new HttpHandlerWrapper(); + requestContext.get(HookSystemFeature.class).hookSystem() + .execute(Hooks.API_ROUTE.hook(), Map.of("apiRoutes", httpExtensions)); + + return httpExtensions; + } } diff --git a/cms-media/src/test/java/com/condation/cms/media/MediaServiceTest.java b/cms-media/src/test/java/com/condation/cms/media/MediaServiceTest.java index e31b746c7..6d15f462e 100644 --- a/cms-media/src/test/java/com/condation/cms/media/MediaServiceTest.java +++ b/cms-media/src/test/java/com/condation/cms/media/MediaServiceTest.java @@ -23,7 +23,6 @@ */ -import com.condation.cms.media.FileMediaService; import java.io.IOException; import java.nio.file.Path; import org.assertj.core.api.Assertions; diff --git a/cms-server/src/main/java/com/condation/cms/server/VHost.java b/cms-server/src/main/java/com/condation/cms/server/VHost.java index 8b792c2ac..973785499 100644 --- a/cms-server/src/main/java/com/condation/cms/server/VHost.java +++ b/cms-server/src/main/java/com/condation/cms/server/VHost.java @@ -75,6 +75,7 @@ import com.condation.cms.server.filter.InitRequestContextFilter; import com.condation.cms.server.filter.PooledRequestContextFilter; import com.condation.cms.server.filter.RequestLoggingFilter; +import com.condation.cms.server.handler.api.APIHandler; import com.condation.cms.server.handler.auth.JettyAuthenticationHandler; import com.condation.cms.server.handler.cache.CacheHandler; import com.condation.cms.server.handler.content.JettyContentHandler; @@ -271,6 +272,10 @@ public Handler buildHttpHandler() { createExtensionHandler() ); + pathMappingsHandler.addMapping(PathSpec.from("/" + APIHandler.PATH + "/*"), + createAPIHandler() + ); + ContextHandler defaultContextHandler = new ContextHandler( pathMappingsHandler, injector.getInstance(SiteProperties.class).contextPath() @@ -301,6 +306,18 @@ public Handler buildHttpHandler() { return hostHandler; } + private Handler.Wrapper createAPIHandler() { + var authHandler = injector.getInstance(JettyAuthenticationHandler.class); + var initContextHandler = injector.getInstance(InitRequestContextFilter.class); + var apiHandler = injector.getInstance(APIHandler.class); + var handlerSequence = new Handler.Sequence( + authHandler, + initContextHandler, + apiHandler + ); + return requestContextFilter(handlerSequence, injector); + } + private Handler.Wrapper createExtensionHandler() { var authHandler = injector.getInstance(JettyAuthenticationHandler.class); var initContextHandler = injector.getInstance(InitRequestContextFilter.class); diff --git a/cms-server/src/main/java/com/condation/cms/server/configs/SiteHandlerModule.java b/cms-server/src/main/java/com/condation/cms/server/configs/SiteHandlerModule.java index ed91e3d4c..9b970c238 100644 --- a/cms-server/src/main/java/com/condation/cms/server/configs/SiteHandlerModule.java +++ b/cms-server/src/main/java/com/condation/cms/server/configs/SiteHandlerModule.java @@ -32,6 +32,7 @@ import com.condation.cms.auth.services.AuthService; import com.condation.cms.auth.services.UserService; import com.condation.cms.media.SiteMediaManager; +import com.condation.cms.server.handler.api.APIHandler; import com.condation.cms.server.handler.auth.JettyAuthenticationHandler; import com.condation.cms.server.filter.InitRequestContextFilter; import com.condation.cms.server.handler.content.JettyContentHandler; @@ -75,6 +76,8 @@ protected void configure() { bind(JettyHttpHandlerExtensionHandler.class).in(Singleton.class); bind(JettyExtensionRouteHandler.class).in(Singleton.class); bind(InitRequestContextFilter.class).in(Singleton.class); + + bind(APIHandler.class).in(Singleton.class); //bind(JettyAuthenticationHandler.class).in(Singleton.class); } diff --git a/cms-server/src/main/java/com/condation/cms/server/handler/api/APIHandler.java b/cms-server/src/main/java/com/condation/cms/server/handler/api/APIHandler.java new file mode 100644 index 000000000..79a1d28ad --- /dev/null +++ b/cms-server/src/main/java/com/condation/cms/server/handler/api/APIHandler.java @@ -0,0 +1,133 @@ +package com.condation.cms.server.handler.api; + +import com.condation.cms.api.extensions.HttpRoutesExtensionPoint; +import com.condation.cms.api.extensions.Mapping; +import com.condation.cms.api.extensions.http.APIHandlerExtensionPoint; +import com.condation.cms.api.extensions.http.PathMapping; + +/*- + * #%L + * cms-server + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.api.request.RequestContext; +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.modules.api.ModuleManager; +import com.google.inject.Inject; + +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; + +/** + * + * @author t.marx + */ +@RequiredArgsConstructor(onConstructor = @__({ + @Inject })) +@Slf4j +public class APIHandler extends Handler.Abstract { + + public static final String PATH = "api"; + + private final ModuleManager moduleManager; + + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception { + + try { + if (handleExtensionRoute(request, response, callback)) { + return true; + } + + if (handleModuleRoute(request, response, callback)) { + return true; + } + + Response.writeError(request, response, callback, 404); + return true; + } catch (Exception e) { + log.error(null, e); + callback.failed(e); + return true; + } + } + + private boolean handleModuleRoute(Request request, Response response, Callback callback) throws Exception { + + String apiRoute = getApiRoute(request); + var method = request.getMethod(); + + Optional firstMatch = moduleManager.extensions(APIHandlerExtensionPoint.class) + .stream() + .filter(extension -> extension.getMapping().getMatchingHandler(apiRoute, method).isPresent()) + .map(extension -> extension.getMapping()) + .findFirst(); + + if (firstMatch.isPresent()) { + var mapping = firstMatch.get(); + var handler = mapping.getMatchingHandler(apiRoute, method).get(); + return handler.handle(request, response, callback); + } + + return false; + } + + private boolean handleExtensionRoute(Request request, Response response, Callback callback) throws Exception { + var requestContext = (RequestContext) request.getAttribute(CreateRequestContextFilter.REQUEST_CONTEXT); + + String extension = getApiRoute(request); + var method = request.getMethod(); + + var httpExtensions = requestContext.get(ServerHooks.class).getAPIRoutes(); + Optional findHttpHandler = httpExtensions.findHttpHandler(method, extension); + + if (findHttpHandler.isPresent()) { + return new JettyHttpHandlerWrapper(findHttpHandler.get().handler()).handle(request, response, callback); + } + + return false; + } + + private String getApiRoute(Request request) { + var path = request.getHttpURI().getPath(); + var contextPath = request.getContext().getContextPath(); + + if (!contextPath.endsWith("/")) { + contextPath += "/"; + } + + contextPath = contextPath + PATH + "/"; + + path = path.replaceFirst(contextPath, ""); + if (!path.startsWith("/")) { + path = "/" + path; + } + return path; + } + +} \ No newline at end of file diff --git a/cms-server/src/main/java/com/condation/cms/server/handler/api/APIRegistry.java b/cms-server/src/main/java/com/condation/cms/server/handler/api/APIRegistry.java new file mode 100644 index 000000000..9d3f87435 --- /dev/null +++ b/cms-server/src/main/java/com/condation/cms/server/handler/api/APIRegistry.java @@ -0,0 +1,41 @@ +package com.condation.cms.server.handler.api; + +/*- + * #%L + * cms-server + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public + * License along with this program. If not, see + * . + * #L% + */ + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiFunction; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; + +public class APIRegistry { + + private Map routes = new HashMap<>(); + + public void register (String path, String method, BiFunction handler) { + routes.put(path, new Route(path, method, handler)); + } + + public static record Route (String path, String method, BiFunction handler) {}; +} diff --git a/cms-server/src/main/java/com/condation/cms/server/handler/extensions/JettyExtensionRouteHandler.java b/cms-server/src/main/java/com/condation/cms/server/handler/extensions/JettyExtensionRouteHandler.java index 8db965cdf..d018d1e8c 100644 --- a/cms-server/src/main/java/com/condation/cms/server/handler/extensions/JettyExtensionRouteHandler.java +++ b/cms-server/src/main/java/com/condation/cms/server/handler/extensions/JettyExtensionRouteHandler.java @@ -27,7 +27,6 @@ import com.condation.cms.extensions.HttpHandlerExtension; import com.condation.cms.extensions.hooks.ServerHooks; import com.condation.cms.extensions.http.JettyHttpHandlerWrapper; -import com.condation.cms.extensions.request.RequestExtensions; import com.condation.cms.server.filter.CreateRequestContextFilter; import java.util.Optional; import lombok.RequiredArgsConstructor; 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 f81e55a5e..a8ed7ec83 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 @@ -27,7 +27,6 @@ import com.condation.cms.extensions.HttpHandlerExtension; import com.condation.cms.extensions.hooks.ServerHooks; import com.condation.cms.extensions.http.JettyHttpHandlerWrapper; -import com.condation.cms.extensions.request.RequestExtensions; import com.condation.cms.server.filter.CreateRequestContextFilter; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -77,7 +76,7 @@ private String getExtensionName(Request request) { } contextPath = contextPath + PATH + "/"; - path = path.replace(contextPath, ""); + path = path.replaceFirst(contextPath, ""); if (!path.startsWith("/")) { path = "/" + path; } diff --git a/modules/example-module/src/main/java/com/condation/cms/modules/example/ExampleApiHandlerExtension.java b/modules/example-module/src/main/java/com/condation/cms/modules/example/ExampleApiHandlerExtension.java new file mode 100644 index 000000000..e4c66f6d2 --- /dev/null +++ b/modules/example-module/src/main/java/com/condation/cms/modules/example/ExampleApiHandlerExtension.java @@ -0,0 +1,64 @@ +package com.condation.cms.modules.example; + +/*- + * #%L + * example-module + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public + * License along with this program. If not, see + * . + * #L% + */ + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +import org.eclipse.jetty.http.pathmap.PathSpec; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; + +import com.condation.cms.api.extensions.http.APIHandlerExtensionPoint; +import com.condation.cms.api.extensions.http.HttpHandler; +import com.condation.cms.api.extensions.http.PathMapping; +import com.condation.modules.api.annotation.Extension; + +import lombok.RequiredArgsConstructor; + +@Extension(APIHandlerExtensionPoint.class) +public class ExampleApiHandlerExtension extends APIHandlerExtensionPoint { + + @Override + public PathMapping getMapping() { + PathMapping mapping = new PathMapping(); + + mapping.add(PathSpec.from("/test-api"), "GET", new ExampleHandler("CondationCMS test api")); + + return mapping; + } + + @RequiredArgsConstructor + private static class ExampleHandler implements HttpHandler { + + private final String message; + + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception { + response.write(true, ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)), callback); + return true; + } + + } +} diff --git a/test-server/hosts/features/extensions/http.extension.js b/test-server/hosts/features/extensions/http.extension.js index 24edf6f8a..bb47765e1 100644 --- a/test-server/hosts/features/extensions/http.extension.js +++ b/test-server/hosts/features/extensions/http.extension.js @@ -24,3 +24,20 @@ $hooks.registerAction("system/server/http/route", (context) => { ) return null; }) + +$hooks.registerAction("system/server/api/route", (context) => { + context.arguments().get("apiRoutes").add( + "GET", + "/hello", + (request, response) => { + + let data = { + "name": "CondationCMS" + } + + response.addHeader("Content-Type", "application/json; charset=utf-8") + response.write(JSON.stringify(data), UTF_8) + } + ) + return null; +}) diff --git a/test-server/modules/example-module/libs/example-module-7.0.0.jar b/test-server/modules/example-module/libs/example-module-7.0.0.jar deleted file mode 100644 index 1e393994352dd7dc440603c46d8fb94930c548ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16683 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 literal 0 HcmV?d00001 diff --git a/test-server/modules/example-module/module.properties b/test-server/modules/example-module/module.properties index 2f1300c1b..3ac324e0c 100644 --- a/test-server/modules/example-module/module.properties +++ b/test-server/modules/example-module/module.properties @@ -1,4 +1,4 @@ id=example-module name=example module -version=7.0.0 +version=7.2.0 priority=HIGH \ No newline at end of file From 29872c7b51855c2a9b332eb1382b09fba9dbaa54 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Sun, 17 Nov 2024 21:02:48 +0100 Subject: [PATCH 02/11] merge handling of routes into single handler --- .../java/com/condation/cms/server/VHost.java | 12 +-- .../cms/server/configs/SiteHandlerModule.java | 27 +++---- .../cms/server/handler/api/APIRegistry.java | 41 ---------- .../JettyHttpHandlerExtensionHandler.java | 1 + .../handler/{api => http}/APIHandler.java | 2 +- .../RoutesHandler.java} | 77 +++++++++++++------ .../handler/module/JettyModuleHandler.java | 1 + .../handler/module/JettyRouteHandler.java | 1 + .../handler/module/JettyRoutesHandler.java | 76 ------------------ 9 files changed, 74 insertions(+), 164 deletions(-) delete mode 100644 cms-server/src/main/java/com/condation/cms/server/handler/api/APIRegistry.java rename cms-server/src/main/java/com/condation/cms/server/handler/{api => http}/APIHandler.java (98%) rename cms-server/src/main/java/com/condation/cms/server/handler/{extensions/JettyExtensionRouteHandler.java => http/RoutesHandler.java} (54%) delete mode 100644 cms-server/src/main/java/com/condation/cms/server/handler/module/JettyRoutesHandler.java diff --git a/cms-server/src/main/java/com/condation/cms/server/VHost.java b/cms-server/src/main/java/com/condation/cms/server/VHost.java index 973785499..ac220d3a8 100644 --- a/cms-server/src/main/java/com/condation/cms/server/VHost.java +++ b/cms-server/src/main/java/com/condation/cms/server/VHost.java @@ -49,13 +49,11 @@ import com.condation.cms.api.eventbus.events.InvalidateTemplateCacheEvent; import com.condation.cms.api.eventbus.events.lifecycle.HostReloadedEvent; import com.condation.cms.api.eventbus.events.lifecycle.HostStoppedEvent; -import com.condation.cms.api.feature.features.ContentRenderFeature; import com.condation.cms.api.feature.features.ThemeFeature; import com.condation.cms.api.module.CMSModuleContext; import com.condation.cms.api.template.TemplateEngine; import com.condation.cms.api.theme.Theme; import com.condation.cms.api.utils.SiteUtil; -import com.condation.cms.content.ContentResolver; import com.condation.cms.core.configuration.ConfigManagement; import com.condation.cms.extensions.GlobalExtensions; import com.condation.cms.extensions.hooks.GlobalHooks; @@ -63,7 +61,6 @@ import com.condation.cms.media.MediaManager; import com.condation.cms.media.SiteMediaManager; import com.condation.cms.media.ThemeMediaManager; -import com.condation.cms.module.DefaultRenderContentFunction; import com.condation.cms.request.RequestContextFactory; import com.condation.cms.server.configs.ModulesModule; import com.condation.cms.server.configs.SiteConfigInitializer; @@ -75,18 +72,17 @@ import com.condation.cms.server.filter.InitRequestContextFilter; import com.condation.cms.server.filter.PooledRequestContextFilter; import com.condation.cms.server.filter.RequestLoggingFilter; -import com.condation.cms.server.handler.api.APIHandler; import com.condation.cms.server.handler.auth.JettyAuthenticationHandler; import com.condation.cms.server.handler.cache.CacheHandler; import com.condation.cms.server.handler.content.JettyContentHandler; import com.condation.cms.server.handler.content.JettyTaxonomyHandler; import com.condation.cms.server.handler.content.JettyViewHandler; -import com.condation.cms.server.handler.extensions.JettyExtensionRouteHandler; import com.condation.cms.server.handler.extensions.JettyHttpHandlerExtensionHandler; +import com.condation.cms.server.handler.http.APIHandler; +import com.condation.cms.server.handler.http.RoutesHandler; import com.condation.cms.server.handler.media.JettyMediaHandler; import com.condation.cms.server.handler.module.JettyModuleHandler; import com.condation.cms.server.handler.module.JettyRouteHandler; -import com.condation.cms.server.handler.module.JettyRoutesHandler; import com.condation.modules.api.ModuleManager; import com.google.inject.Injector; import com.google.inject.Key; @@ -227,8 +223,7 @@ public Handler buildHttpHandler() { var taxonomyHandler = injector.getInstance(JettyTaxonomyHandler.class); var viewHandler = injector.getInstance(JettyViewHandler.class); var routeHandler = injector.getInstance(JettyRouteHandler.class); - var routesHandler = injector.getInstance(JettyRoutesHandler.class); - var extensionRouteHandler = injector.getInstance(JettyExtensionRouteHandler.class); + var routesHandler = injector.getInstance(RoutesHandler.class); var authHandler = injector.getInstance(JettyAuthenticationHandler.class); var initContextHandler = injector.getInstance(InitRequestContextFilter.class); @@ -237,7 +232,6 @@ public Handler buildHttpHandler() { initContextHandler, routeHandler, routesHandler, - extensionRouteHandler, viewHandler, taxonomyHandler, contentHandler diff --git a/cms-server/src/main/java/com/condation/cms/server/configs/SiteHandlerModule.java b/cms-server/src/main/java/com/condation/cms/server/configs/SiteHandlerModule.java index 9b970c238..e89a99006 100644 --- a/cms-server/src/main/java/com/condation/cms/server/configs/SiteHandlerModule.java +++ b/cms-server/src/main/java/com/condation/cms/server/configs/SiteHandlerModule.java @@ -1,5 +1,13 @@ package com.condation.cms.server.configs; +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.jetty.server.handler.ResourceHandler; + /*- * #%L * cms-server @@ -32,31 +40,25 @@ import com.condation.cms.auth.services.AuthService; import com.condation.cms.auth.services.UserService; import com.condation.cms.media.SiteMediaManager; -import com.condation.cms.server.handler.api.APIHandler; -import com.condation.cms.server.handler.auth.JettyAuthenticationHandler; +import com.condation.cms.server.FileFolderPathResource; import com.condation.cms.server.filter.InitRequestContextFilter; +import com.condation.cms.server.handler.auth.JettyAuthenticationHandler; import com.condation.cms.server.handler.content.JettyContentHandler; import com.condation.cms.server.handler.content.JettyTaxonomyHandler; import com.condation.cms.server.handler.content.JettyViewHandler; import com.condation.cms.server.handler.extensions.JettyHttpHandlerExtensionHandler; -import com.condation.cms.server.handler.extensions.JettyExtensionRouteHandler; +import com.condation.cms.server.handler.http.APIHandler; +import com.condation.cms.server.handler.http.RoutesHandler; import com.condation.cms.server.handler.media.JettyMediaHandler; import com.condation.cms.server.handler.module.JettyModuleHandler; import com.condation.cms.server.handler.module.JettyRouteHandler; -import com.condation.cms.server.handler.module.JettyRoutesHandler; -import com.condation.cms.server.FileFolderPathResource; import com.condation.modules.api.ModuleManager; import com.google.inject.AbstractModule; import com.google.inject.Provides; import com.google.inject.Singleton; import com.google.inject.name.Named; -import java.io.IOException; -import java.nio.file.Path; -import java.time.Duration; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; + import lombok.RequiredArgsConstructor; -import org.eclipse.jetty.server.handler.ResourceHandler; /** * @@ -72,9 +74,8 @@ protected void configure() { bind(JettyContentHandler.class).in(Singleton.class); bind(JettyTaxonomyHandler.class).in(Singleton.class); bind(JettyRouteHandler.class).in(Singleton.class); - bind(JettyRoutesHandler.class).in(Singleton.class); + bind(RoutesHandler.class).in(Singleton.class); bind(JettyHttpHandlerExtensionHandler.class).in(Singleton.class); - bind(JettyExtensionRouteHandler.class).in(Singleton.class); bind(InitRequestContextFilter.class).in(Singleton.class); bind(APIHandler.class).in(Singleton.class); diff --git a/cms-server/src/main/java/com/condation/cms/server/handler/api/APIRegistry.java b/cms-server/src/main/java/com/condation/cms/server/handler/api/APIRegistry.java deleted file mode 100644 index 9d3f87435..000000000 --- a/cms-server/src/main/java/com/condation/cms/server/handler/api/APIRegistry.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.condation.cms.server.handler.api; - -/*- - * #%L - * cms-server - * %% - * Copyright (C) 2023 - 2024 CondationCMS - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public - * License along with this program. If not, see - * . - * #L% - */ - -import java.util.HashMap; -import java.util.Map; -import java.util.function.BiFunction; - -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Response; - -public class APIRegistry { - - private Map routes = new HashMap<>(); - - public void register (String path, String method, BiFunction handler) { - routes.put(path, new Route(path, method, handler)); - } - - public static record Route (String path, String method, BiFunction handler) {}; -} 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 a8ed7ec83..d952e2659 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 @@ -42,6 +42,7 @@ */ @RequiredArgsConstructor @Slf4j +@Deprecated(since = "7.3.0", forRemoval = true) public class JettyHttpHandlerExtensionHandler extends Handler.Abstract { public static final String PATH = "extension"; diff --git a/cms-server/src/main/java/com/condation/cms/server/handler/api/APIHandler.java b/cms-server/src/main/java/com/condation/cms/server/handler/http/APIHandler.java similarity index 98% rename from cms-server/src/main/java/com/condation/cms/server/handler/api/APIHandler.java rename to cms-server/src/main/java/com/condation/cms/server/handler/http/APIHandler.java index 79a1d28ad..9ac101e72 100644 --- a/cms-server/src/main/java/com/condation/cms/server/handler/api/APIHandler.java +++ b/cms-server/src/main/java/com/condation/cms/server/handler/http/APIHandler.java @@ -1,4 +1,4 @@ -package com.condation.cms.server.handler.api; +package com.condation.cms.server.handler.http; import com.condation.cms.api.extensions.HttpRoutesExtensionPoint; import com.condation.cms.api.extensions.Mapping; diff --git a/cms-server/src/main/java/com/condation/cms/server/handler/extensions/JettyExtensionRouteHandler.java b/cms-server/src/main/java/com/condation/cms/server/handler/http/RoutesHandler.java similarity index 54% rename from cms-server/src/main/java/com/condation/cms/server/handler/extensions/JettyExtensionRouteHandler.java rename to cms-server/src/main/java/com/condation/cms/server/handler/http/RoutesHandler.java index d018d1e8c..518069622 100644 --- a/cms-server/src/main/java/com/condation/cms/server/handler/extensions/JettyExtensionRouteHandler.java +++ b/cms-server/src/main/java/com/condation/cms/server/handler/http/RoutesHandler.java @@ -1,4 +1,4 @@ -package com.condation.cms.server.handler.extensions; +package com.condation.cms.server.handler.http; /*- * #%L @@ -22,12 +22,16 @@ * #L% */ - +import com.condation.cms.api.extensions.HttpRoutesExtensionPoint; +import com.condation.cms.api.extensions.Mapping; 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.modules.api.ModuleManager; +import com.google.inject.Inject; import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -40,39 +44,64 @@ * * @author t.marx */ -@RequiredArgsConstructor +@RequiredArgsConstructor(onConstructor = @__({ + @Inject })) @Slf4j -public class JettyExtensionRouteHandler extends Handler.Abstract { +public class RoutesHandler extends Handler.Abstract { + + private final ModuleManager moduleManager; @Override public boolean handle(Request request, Response response, Callback callback) throws Exception { - var requestContext = (RequestContext) request.getAttribute(CreateRequestContextFilter.REQUEST_CONTEXT); - String extension = getExtensionName(request); - var method = request.getMethod(); - - var httpExtensions = requestContext.get(ServerHooks.class).getHttpRoutes(); - Optional findHttpHandler = httpExtensions.findHttpHandler(method, extension); - - if (findHttpHandler.isPresent()) { - return new JettyHttpHandlerWrapper(findHttpHandler.get().handler()).handle(request, response, callback); + try { + if (tryExtensionRoutes(request, response, callback)) { + return true; + } + + if (tryModuleRoutes(request, response, callback)) { + return true; + } + + Response.writeError(request, response, callback, 404); + return true; + } catch (Exception e) { + log.error(null, e); + callback.failed(e); + return true; } - return false; } - private String getExtensionName(Request request) { - var path = request.getHttpURI().getPath(); - var contextPath = request.getContext().getContextPath(); + private boolean tryModuleRoutes(Request request, Response response, Callback callback) throws Exception { + String route = "/" + RequestUtil.getContentPath(request); - if (!contextPath.endsWith("/")) { - contextPath += "/"; - } + Optional firstMatch = moduleManager.extensions(HttpRoutesExtensionPoint.class) + .stream() + .filter(extension -> extension.getMapping().getMatchingHandler(route).isPresent()) + .map(extension -> extension.getMapping()) + .findFirst(); - path = path.replace(contextPath, ""); - if (!path.startsWith("/")) { - path = "/" + path; + if (firstMatch.isPresent()) { + var mapping = firstMatch.get(); + var handler = mapping.getMatchingHandler(route).get(); + return handler.handle(request, response, callback); } - return path; + + return false; } + private boolean tryExtensionRoutes(Request request, Response response, Callback callback) throws Exception { + var requestContext = (RequestContext) request.getAttribute(CreateRequestContextFilter.REQUEST_CONTEXT); + + String route = "/" + RequestUtil.getContentPath(request); + var method = request.getMethod(); + + var httpExtensions = requestContext.get(ServerHooks.class).getHttpRoutes(); + Optional findHttpHandler = httpExtensions.findHttpHandler(method, route); + + if (findHttpHandler.isPresent()) { + return new JettyHttpHandlerWrapper(findHttpHandler.get().handler()).handle(request, response, callback); + } + return false; + } } diff --git a/cms-server/src/main/java/com/condation/cms/server/handler/module/JettyModuleHandler.java b/cms-server/src/main/java/com/condation/cms/server/handler/module/JettyModuleHandler.java index 115e733b6..3b9183965 100644 --- a/cms-server/src/main/java/com/condation/cms/server/handler/module/JettyModuleHandler.java +++ b/cms-server/src/main/java/com/condation/cms/server/handler/module/JettyModuleHandler.java @@ -41,6 +41,7 @@ */ @RequiredArgsConstructor @Slf4j +@Deprecated(since = "7.3.0", forRemoval = true) public class JettyModuleHandler extends Handler.Abstract { public static final String PATH = "module"; diff --git a/cms-server/src/main/java/com/condation/cms/server/handler/module/JettyRouteHandler.java b/cms-server/src/main/java/com/condation/cms/server/handler/module/JettyRouteHandler.java index a5c44a209..97a6cb289 100644 --- a/cms-server/src/main/java/com/condation/cms/server/handler/module/JettyRouteHandler.java +++ b/cms-server/src/main/java/com/condation/cms/server/handler/module/JettyRouteHandler.java @@ -42,6 +42,7 @@ @RequiredArgsConstructor(onConstructor = @__({ @Inject})) @Slf4j +@Deprecated(since = "7.3.0", forRemoval = true) public class JettyRouteHandler extends Handler.Abstract { private final ModuleManager moduleManager; diff --git a/cms-server/src/main/java/com/condation/cms/server/handler/module/JettyRoutesHandler.java b/cms-server/src/main/java/com/condation/cms/server/handler/module/JettyRoutesHandler.java deleted file mode 100644 index 93ae373e2..000000000 --- a/cms-server/src/main/java/com/condation/cms/server/handler/module/JettyRoutesHandler.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.condation.cms.server.handler.module; - -/*- - * #%L - * cms-server - * %% - * Copyright (C) 2023 - 2024 CondationCMS - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public - * License along with this program. If not, see - * . - * #L% - */ - - -import com.condation.cms.api.extensions.HttpRoutesExtensionPoint; -import com.condation.cms.api.extensions.Mapping; -import com.condation.cms.api.utils.RequestUtil; -import com.condation.modules.api.ModuleManager; -import com.google.inject.Inject; -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; - -/** - * - * @author t.marx - */ -@RequiredArgsConstructor(onConstructor = @__({ - @Inject})) -@Slf4j -public class JettyRoutesHandler extends Handler.Abstract { - - private final ModuleManager moduleManager; - - @Override - public boolean handle(Request request, Response response, Callback callback) throws Exception { - - try { - String route = "/" + RequestUtil.getContentPath(request); - - Optional firstMatch = moduleManager.extensions(HttpRoutesExtensionPoint.class) - .stream() - .filter(extension -> extension.getMapping().getMatchingHandler(route).isPresent()) - .map(extension -> extension.getMapping()) - .findFirst(); - - if (firstMatch.isPresent()) { - var mapping = firstMatch.get(); - var handler = mapping.getMatchingHandler(route).get(); - return handler.handle(request, response, callback); - } - - - return false; - } catch (Exception e) { - log.error(null, e); - callback.failed(e); - return true; - } - } -} From a81847ed7c34042b5da9e287c1f12accd5e4e513 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Sun, 17 Nov 2024 21:16:30 +0100 Subject: [PATCH 03/11] deprecated old api --- .../extensions/HttpHandlerExtensionPoint.java | 1 + .../extensions/HttpRouteExtensionPoint.java | 1 + .../core/scheduler/SingleCronJobRunner.java | 21 ++++++++++++++----- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/HttpHandlerExtensionPoint.java b/cms-api/src/main/java/com/condation/cms/api/extensions/HttpHandlerExtensionPoint.java index a4d8c586e..9e97f7e14 100644 --- a/cms-api/src/main/java/com/condation/cms/api/extensions/HttpHandlerExtensionPoint.java +++ b/cms-api/src/main/java/com/condation/cms/api/extensions/HttpHandlerExtensionPoint.java @@ -27,6 +27,7 @@ * * @author t.marx */ +@Deprecated(since = "7.3.0", forRemoval = true) public abstract class HttpHandlerExtensionPoint extends AbstractExtensionPoint { abstract public Mapping getMapping(); } diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/HttpRouteExtensionPoint.java b/cms-api/src/main/java/com/condation/cms/api/extensions/HttpRouteExtensionPoint.java index fb588b906..48c8095c2 100644 --- a/cms-api/src/main/java/com/condation/cms/api/extensions/HttpRouteExtensionPoint.java +++ b/cms-api/src/main/java/com/condation/cms/api/extensions/HttpRouteExtensionPoint.java @@ -31,6 +31,7 @@ * * @author t.marx */ +@Deprecated(since = "7.3.0", forRemoval = true) public abstract class HttpRouteExtensionPoint extends AbstractExtensionPoint { abstract public String getRoute (); diff --git a/cms-core/src/main/java/com/condation/cms/core/scheduler/SingleCronJobRunner.java b/cms-core/src/main/java/com/condation/cms/core/scheduler/SingleCronJobRunner.java index 00c19fb28..932d22ccd 100644 --- a/cms-core/src/main/java/com/condation/cms/core/scheduler/SingleCronJobRunner.java +++ b/cms-core/src/main/java/com/condation/cms/core/scheduler/SingleCronJobRunner.java @@ -21,11 +21,12 @@ * . * #L% */ - import com.condation.cms.api.scheduler.CronJob; import com.condation.cms.api.scheduler.CronJobContext; +import java.util.concurrent.Semaphore; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import lombok.extern.slf4j.Slf4j; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; @@ -36,19 +37,29 @@ * * @author t.marx */ +@Slf4j @DisallowConcurrentExecution public class SingleCronJobRunner implements Job { public static final String DATA_CRONJOB = "cronjob"; public static final String DATA_CONTEXT = "context"; + public static final Semaphore LOCK = new Semaphore(1, true); + @Override public void execute(JobExecutionContext context) throws JobExecutionException { - CronJobContext jobContext = (CronJobContext) context.getJobDetail().getJobDataMap().get(DATA_CONTEXT); - CronJob cronJob = (CronJob) context.getJobDetail().getJobDataMap().get(DATA_CRONJOB); + try { + LOCK.acquire(); + CronJobContext jobContext = (CronJobContext) context.getJobDetail().getJobDataMap().get(DATA_CONTEXT); + CronJob cronJob = (CronJob) context.getJobDetail().getJobDataMap().get(DATA_CRONJOB); - if (cronJob != null && jobContext != null) { - cronJob.accept(jobContext); + if (cronJob != null && jobContext != null) { + cronJob.accept(jobContext); + } + } catch (Exception e) { + log.error("", e); + } finally { + LOCK.release(); } } From c28bd0547f5c42adf2af82bedc0e59fca4ffe0b9 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Mon, 18 Nov 2024 14:08:25 +0100 Subject: [PATCH 04/11] remove deprecations --- .../condation/cms/api/extensions/HttpHandlerExtensionPoint.java | 1 - .../handler/extensions/JettyHttpHandlerExtensionHandler.java | 1 - .../condation/cms/server/handler/module/JettyModuleHandler.java | 1 - 3 files changed, 3 deletions(-) diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/HttpHandlerExtensionPoint.java b/cms-api/src/main/java/com/condation/cms/api/extensions/HttpHandlerExtensionPoint.java index 9e97f7e14..a4d8c586e 100644 --- a/cms-api/src/main/java/com/condation/cms/api/extensions/HttpHandlerExtensionPoint.java +++ b/cms-api/src/main/java/com/condation/cms/api/extensions/HttpHandlerExtensionPoint.java @@ -27,7 +27,6 @@ * * @author t.marx */ -@Deprecated(since = "7.3.0", forRemoval = true) public abstract class HttpHandlerExtensionPoint extends AbstractExtensionPoint { abstract public Mapping getMapping(); } 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 d952e2659..a8ed7ec83 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 @@ -42,7 +42,6 @@ */ @RequiredArgsConstructor @Slf4j -@Deprecated(since = "7.3.0", forRemoval = true) public class JettyHttpHandlerExtensionHandler extends Handler.Abstract { public static final String PATH = "extension"; diff --git a/cms-server/src/main/java/com/condation/cms/server/handler/module/JettyModuleHandler.java b/cms-server/src/main/java/com/condation/cms/server/handler/module/JettyModuleHandler.java index 3b9183965..115e733b6 100644 --- a/cms-server/src/main/java/com/condation/cms/server/handler/module/JettyModuleHandler.java +++ b/cms-server/src/main/java/com/condation/cms/server/handler/module/JettyModuleHandler.java @@ -41,7 +41,6 @@ */ @RequiredArgsConstructor @Slf4j -@Deprecated(since = "7.3.0", forRemoval = true) public class JettyModuleHandler extends Handler.Abstract { public static final String PATH = "module"; From a54b1a765c665b7e2aedc64e7f3fcc4a86c5f148 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Tue, 19 Nov 2024 17:15:27 +0100 Subject: [PATCH 05/11] add system module --- cms-server/pom.xml | 5 +++++ modules/pom.xml | 1 + modules/system-modules/pom.xml | 18 ++++++++++++++++++ .../cms/modules/system}/AuthShortCodes.java | 2 +- pom.xml | 5 +++++ 5 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 modules/system-modules/pom.xml rename {cms-auth/src/main/java/com/condation/cms/auth => modules/system-modules/src/main/java/com/condation/cms/modules/system}/AuthShortCodes.java (97%) diff --git a/cms-server/pom.xml b/cms-server/pom.xml index 3f9b86b90..c19b3c4b5 100644 --- a/cms-server/pom.xml +++ b/cms-server/pom.xml @@ -50,6 +50,11 @@ cms-auth + + com.condation.cms.modules + cms-system-modules + + com.condation.cms cms-test diff --git a/modules/pom.xml b/modules/pom.xml index b76b50635..e95241be0 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -12,5 +12,6 @@ example-module + system-modules diff --git a/modules/system-modules/pom.xml b/modules/system-modules/pom.xml new file mode 100644 index 000000000..9d92e8140 --- /dev/null +++ b/modules/system-modules/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + + com.condation.cms.modules + cms-modules + 7.2.0 + + cms-system-modules + jar + + + + com.condation.cms + cms-api + + + \ No newline at end of file diff --git a/cms-auth/src/main/java/com/condation/cms/auth/AuthShortCodes.java b/modules/system-modules/src/main/java/com/condation/cms/modules/system/AuthShortCodes.java similarity index 97% rename from cms-auth/src/main/java/com/condation/cms/auth/AuthShortCodes.java rename to modules/system-modules/src/main/java/com/condation/cms/modules/system/AuthShortCodes.java index 7f14f8e12..b15a624be 100644 --- a/cms-auth/src/main/java/com/condation/cms/auth/AuthShortCodes.java +++ b/modules/system-modules/src/main/java/com/condation/cms/modules/system/AuthShortCodes.java @@ -1,4 +1,4 @@ -package com.condation.cms.auth; +package com.condation.cms.modules.system; /*- * #%L diff --git a/pom.xml b/pom.xml index ebec4cb7a..9944cfe1f 100644 --- a/pom.xml +++ b/pom.xml @@ -116,6 +116,11 @@ modules-api ${project.version} + + com.condation.cms.modules + cms-system-modules + ${project.version} + org.apache.commons From fc9831d79fd636a5a2699b1390bd7073a593e13b Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Tue, 19 Nov 2024 20:14:39 +0100 Subject: [PATCH 06/11] fix handler --- .../condation/cms/server/handler/http/RoutesHandler.java | 3 +-- .../com/condation/cms/modules/system/ApiEndpoints.java | 9 +++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiEndpoints.java 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 518069622..2171ded65 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 @@ -63,8 +63,7 @@ public boolean handle(Request request, Response response, Callback callback) thr return true; } - Response.writeError(request, response, callback, 404); - return true; + return false; } catch (Exception e) { log.error(null, e); callback.failed(e); diff --git a/modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiEndpoints.java b/modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiEndpoints.java new file mode 100644 index 000000000..6b0bf651e --- /dev/null +++ b/modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiEndpoints.java @@ -0,0 +1,9 @@ +package com.condation.cms.modules.system; + +/** + * + * @author thmar + */ +public class ApiEndpoints { + +} From a06f1df9e84432cdc106a666f63e30f460cc1de6 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Tue, 19 Nov 2024 20:53:42 +0100 Subject: [PATCH 07/11] add content handler --- .../cms/api/extensions/http/PathMapping.java | 2 - .../cms/server/configs/ModulesModule.java | 13 --- .../cms/modules/system/ApiContentHandler.java | 100 ++++++++++++++++++ .../cms/modules/system/ApiEndpoints.java | 44 +++++++- 4 files changed, 143 insertions(+), 16 deletions(-) create mode 100644 modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiContentHandler.java diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/http/PathMapping.java b/cms-api/src/main/java/com/condation/cms/api/extensions/http/PathMapping.java index 11f297d6c..05afa9144 100644 --- a/cms-api/src/main/java/com/condation/cms/api/extensions/http/PathMapping.java +++ b/cms-api/src/main/java/com/condation/cms/api/extensions/http/PathMapping.java @@ -23,9 +23,7 @@ */ import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import org.eclipse.jetty.http.pathmap.PathSpec; diff --git a/cms-server/src/main/java/com/condation/cms/server/configs/ModulesModule.java b/cms-server/src/main/java/com/condation/cms/server/configs/ModulesModule.java index ab88cc35a..b21444f34 100644 --- a/cms-server/src/main/java/com/condation/cms/server/configs/ModulesModule.java +++ b/cms-server/src/main/java/com/condation/cms/server/configs/ModulesModule.java @@ -21,32 +21,19 @@ * . * #L% */ -import com.condation.cms.api.ServerProperties; import com.condation.cms.api.SiteProperties; -import com.condation.cms.api.configuration.Configuration; -import com.condation.cms.api.eventbus.EventBus; import com.condation.cms.api.extensions.MarkdownRendererProviderExtensionPoint; import com.condation.cms.api.extensions.TemplateEngineProviderExtensionPoint; import com.condation.cms.api.extensions.TemplateEngineProviderExtentionPoint; -import com.condation.cms.api.feature.features.ConfigurationFeature; -import com.condation.cms.api.feature.features.CronJobSchedulerFeature; -import com.condation.cms.api.feature.features.DBFeature; -import com.condation.cms.api.feature.features.EventBusFeature; -import com.condation.cms.api.feature.features.MessagingFeature; import com.condation.cms.api.feature.features.ModuleManagerFeature; -import com.condation.cms.api.feature.features.ServerPropertiesFeature; -import com.condation.cms.api.feature.features.SitePropertiesFeature; -import com.condation.cms.api.feature.features.ThemeFeature; import com.condation.cms.api.hooks.HookSystem; import com.condation.cms.api.markdown.MarkdownRenderer; -import com.condation.cms.api.messaging.Messaging; import com.condation.cms.api.module.CMSModuleContext; import com.condation.cms.api.module.CMSRequestContext; import com.condation.cms.api.request.ThreadLocalRequestContext; import com.condation.cms.api.template.TemplateEngine; import com.condation.cms.api.theme.Theme; import com.condation.cms.content.markdown.module.CMSMarkdownRenderer; -import com.condation.cms.core.scheduler.SiteCronJobScheduler; import com.condation.cms.filesystem.FileDB; import com.condation.modules.api.ModuleManager; import com.condation.modules.api.ModuleRequestContextFactory; diff --git a/modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiContentHandler.java b/modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiContentHandler.java new file mode 100644 index 000000000..10816de47 --- /dev/null +++ b/modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiContentHandler.java @@ -0,0 +1,100 @@ +package com.condation.cms.modules.system; + +/*- + * #%L + * cms-system-modules + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.api.db.ContentNode; +import com.condation.cms.api.db.DB; +import com.condation.cms.api.db.cms.ReadOnlyFile; +import com.condation.cms.api.extensions.http.HttpHandler; +import com.condation.cms.api.utils.PathUtil; +import com.condation.cms.api.utils.RequestUtil; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.util.Optional; +import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; + +/** + * + * @author thmar + */ +public class ApiContentHandler implements HttpHandler { + + private final DB db; + public static final Gson GSON = new GsonBuilder() + .enableComplexMapKeySerialization() + .create(); + + public ApiContentHandler(DB db) { + this.db = db; + } + + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception { + var uri = RequestUtil.getContentPath(request); + uri = uri.replaceFirst("api/content/", ""); + + var resolved = resolveContentNode(uri); + if (resolved.isEmpty()) { + Response.writeError(request, response, callback, 404); + return true; + } + + Content.Sink.write(response, true, GSON.toJson(resolved.get().data()), callback); + + return true; + } + + private Optional resolveContentNode(String uri) { + var contentBase = db.getReadOnlyFileSystem().contentBase(); + var contentPath = contentBase.resolve(uri); + ReadOnlyFile contentFile = null; + if (contentPath.exists() && contentPath.isDirectory()) { + // use index.md + var tempFile = contentPath.resolve("index.md"); + if (tempFile.exists()) { + contentFile = tempFile; + } else { + return Optional.empty(); + } + } else { + var temp = contentBase.resolve(uri + ".md"); + if (temp.exists()) { + contentFile = temp; + } else { + return Optional.empty(); + } + } + + var filePath = PathUtil.toRelativeFile(contentFile, contentBase); + if (!db.getContent().isVisible(filePath)) { + return Optional.empty(); + } + + final ContentNode contentNode = db.getContent().byUri(filePath).get(); + + return Optional.ofNullable(contentNode); + } +} diff --git a/modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiEndpoints.java b/modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiEndpoints.java index 6b0bf651e..ee6b998e5 100644 --- a/modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiEndpoints.java +++ b/modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiEndpoints.java @@ -1,9 +1,51 @@ package com.condation.cms.modules.system; +/*- + * #%L + * cms-system-modules + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.api.extensions.http.APIHandlerExtensionPoint; +import com.condation.cms.api.extensions.http.PathMapping; +import com.condation.cms.api.feature.features.DBFeature; +import com.condation.modules.api.annotation.Extension; +import org.eclipse.jetty.http.pathmap.PathSpec; + /** * * @author thmar */ -public class ApiEndpoints { +@Extension(APIHandlerExtensionPoint.class) +public class ApiEndpoints extends APIHandlerExtensionPoint { + + @Override + public PathMapping getMapping() { + var mapping = new PathMapping(); + + mapping.add( + PathSpec.from("/content/*"), + "GET", + new ApiContentHandler(getContext().get(DBFeature.class).db()) + ); + + return mapping; + } } From f4683e52add330c34b9a4b572d41303bc5d47b6a Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Wed, 20 Nov 2024 16:38:11 +0100 Subject: [PATCH 08/11] api versioning endpoint for navigation tree deactivate api by default and make it activatable for site --- .../cms/api/db/cms/NIOReadOnlyFile.java | 2 +- .../cms/server/handler/http/APIHandler.java | 15 +++- .../system/{ => handlers}/ApiEndpoints.java | 14 +++- .../v1/ContentHandler.java} | 13 ++- .../system/handlers/v1/NavigationHandler.java | 84 +++++++++++++++++++ test-server/hosts/features/site.toml | 3 + 6 files changed, 121 insertions(+), 10 deletions(-) rename modules/system-modules/src/main/java/com/condation/cms/modules/system/{ => handlers}/ApiEndpoints.java (75%) rename modules/system-modules/src/main/java/com/condation/cms/modules/system/{ApiContentHandler.java => handlers/v1/ContentHandler.java} (87%) create mode 100644 modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/NavigationHandler.java diff --git a/cms-api/src/main/java/com/condation/cms/api/db/cms/NIOReadOnlyFile.java b/cms-api/src/main/java/com/condation/cms/api/db/cms/NIOReadOnlyFile.java index ebc97ea71..7ac2e682a 100644 --- a/cms-api/src/main/java/com/condation/cms/api/db/cms/NIOReadOnlyFile.java +++ b/cms-api/src/main/java/com/condation/cms/api/db/cms/NIOReadOnlyFile.java @@ -116,7 +116,7 @@ public ReadOnlyFile getParent() { @Override public List children() throws IOException { - return Files.list(file).map(path -> new NIOReadOnlyFile(file, basePath)).map(ReadOnlyFile.class::cast).toList(); + return Files.list(file).map(child -> new NIOReadOnlyFile(child, basePath)).map(ReadOnlyFile.class::cast).toList(); } @Override 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 9ac101e72..f205548d7 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 @@ -1,9 +1,11 @@ package com.condation.cms.server.handler.http; +import com.condation.cms.api.configuration.configs.SiteConfiguration; import com.condation.cms.api.extensions.HttpRoutesExtensionPoint; import com.condation.cms.api.extensions.Mapping; import com.condation.cms.api.extensions.http.APIHandlerExtensionPoint; import com.condation.cms.api.extensions.http.PathMapping; +import com.condation.cms.api.feature.features.ConfigurationFeature; /*- * #%L @@ -56,9 +58,20 @@ public class APIHandler extends Handler.Abstract { private final ModuleManager moduleManager; + private boolean isApiActivated (Request request) { + var requestContext = (RequestContext) request.getAttribute(CreateRequestContextFilter.REQUEST_CONTEXT); + var siteProperties = requestContext.get(ConfigurationFeature.class).configuration().get(SiteConfiguration.class).siteProperties(); + + return siteProperties.getOrDefault("api.enabled", Boolean.FALSE); + } + @Override public boolean handle(Request request, Response response, Callback callback) throws Exception { - + + if (!isApiActivated(request)) { + return false; + } + try { if (handleExtensionRoute(request, response, callback)) { return true; diff --git a/modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiEndpoints.java b/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/ApiEndpoints.java similarity index 75% rename from modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiEndpoints.java rename to modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/ApiEndpoints.java index ee6b998e5..f05a952f8 100644 --- a/modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiEndpoints.java +++ b/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/ApiEndpoints.java @@ -1,4 +1,4 @@ -package com.condation.cms.modules.system; +package com.condation.cms.modules.system.handlers; /*- * #%L @@ -22,9 +22,11 @@ * #L% */ +import com.condation.cms.modules.system.handlers.v1.ContentHandler; import com.condation.cms.api.extensions.http.APIHandlerExtensionPoint; import com.condation.cms.api.extensions.http.PathMapping; import com.condation.cms.api.feature.features.DBFeature; +import com.condation.cms.modules.system.handlers.v1.NavigationHandler; import com.condation.modules.api.annotation.Extension; import org.eclipse.jetty.http.pathmap.PathSpec; @@ -39,10 +41,14 @@ public class ApiEndpoints extends APIHandlerExtensionPoint { public PathMapping getMapping() { var mapping = new PathMapping(); - mapping.add( - PathSpec.from("/content/*"), + mapping.add(PathSpec.from("/v1/content/*"), "GET", - new ApiContentHandler(getContext().get(DBFeature.class).db()) + new ContentHandler(getContext().get(DBFeature.class).db()) + ); + + mapping.add(PathSpec.from("/v1/navigation/*"), + "GET", + new NavigationHandler(getContext().get(DBFeature.class).db()) ); return mapping; diff --git a/modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiContentHandler.java b/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/ContentHandler.java similarity index 87% rename from modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiContentHandler.java rename to modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/ContentHandler.java index 10816de47..cec8f7ca8 100644 --- a/modules/system-modules/src/main/java/com/condation/cms/modules/system/ApiContentHandler.java +++ b/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/ContentHandler.java @@ -1,4 +1,4 @@ -package com.condation.cms.modules.system; +package com.condation.cms.modules.system.handlers.v1; /*- * #%L @@ -31,6 +31,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.util.Optional; +import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.io.Content; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; @@ -40,21 +41,24 @@ * * @author thmar */ -public class ApiContentHandler implements HttpHandler { +public class ContentHandler implements HttpHandler { private final DB db; public static final Gson GSON = new GsonBuilder() .enableComplexMapKeySerialization() .create(); - public ApiContentHandler(DB db) { + public ContentHandler(DB db) { this.db = db; } @Override public boolean handle(Request request, Response response, Callback callback) throws Exception { var uri = RequestUtil.getContentPath(request); - uri = uri.replaceFirst("api/content/", ""); + uri = uri.replaceFirst("api/v1/content", ""); + if (uri.startsWith("/")) { + uri = uri.substring(1); + } var resolved = resolveContentNode(uri); if (resolved.isEmpty()) { @@ -62,6 +66,7 @@ public boolean handle(Request request, Response response, Callback callback) thr return true; } + response.getHeaders().add(HttpHeader.CONTENT_TYPE, "application/json; charset=utf-8"); Content.Sink.write(response, true, GSON.toJson(resolved.get().data()), callback); return true; diff --git a/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/NavigationHandler.java b/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/NavigationHandler.java new file mode 100644 index 000000000..10c96cb2c --- /dev/null +++ b/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/NavigationHandler.java @@ -0,0 +1,84 @@ +package com.condation.cms.modules.system.handlers.v1; + +/*- + * #%L + * cms-system-modules + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.api.db.DB; +import com.condation.cms.api.extensions.http.HttpHandler; +import com.condation.cms.api.utils.RequestUtil; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; + +/** + * + * @author thmar + */ +public class NavigationHandler implements HttpHandler { + + private final DB db; + public static final Gson GSON = new GsonBuilder() + .enableComplexMapKeySerialization() + .create(); + + public NavigationHandler(DB db) { + this.db = db; + } + + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception { + var uri = RequestUtil.getContentPath(request); + uri = uri.replaceFirst("api/v1/navigation", ""); + if (uri.startsWith("/")) { + uri = uri.substring(1); + } + + var file = db.getReadOnlyFileSystem().contentBase().resolve(uri); + + if (!file.exists()) { + Response.writeError(request, response, callback, 404); + return true; + } + + List children = file.children().stream().map(child -> new NavNode(child.getFileName())).toList(); + + NavNode node = new NavNode(file.getFileName(), children); + + response.getHeaders().add(HttpHeader.CONTENT_TYPE, "application/json; charset=utf-8"); + Content.Sink.write(response, true, GSON.toJson(node), callback); + + return true; + } + + private static record NavNode (String path, List children) { + public NavNode (String path) { + this(path, Collections.emptyList()); + } + }; +} diff --git a/test-server/hosts/features/site.toml b/test-server/hosts/features/site.toml index 61882d1d3..124559762 100644 --- a/test-server/hosts/features/site.toml +++ b/test-server/hosts/features/site.toml @@ -13,3 +13,6 @@ mode = "PERSISTENT" [modules] active = [ "forms-module" ] + +[api] +enabled = true \ No newline at end of file From f37c9551192d16307833e59b7cd7cb9111aa4779 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Thu, 21 Nov 2024 09:17:35 +0100 Subject: [PATCH 09/11] load content Nodes --- modules/system-modules/pom.xml | 4 +++ .../system/handlers/v1/NavigationHandler.java | 26 ++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/modules/system-modules/pom.xml b/modules/system-modules/pom.xml index 9d92e8140..e2565d1b9 100644 --- a/modules/system-modules/pom.xml +++ b/modules/system-modules/pom.xml @@ -14,5 +14,9 @@ com.condation.cms cms-api + + com.condation.cms + cms-filesystem + \ No newline at end of file diff --git a/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/NavigationHandler.java b/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/NavigationHandler.java index 10c96cb2c..eab7113ef 100644 --- a/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/NavigationHandler.java +++ b/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/NavigationHandler.java @@ -22,16 +22,22 @@ * #L% */ +import com.condation.cms.api.db.ContentNode; import com.condation.cms.api.db.DB; +import com.condation.cms.api.db.cms.ReadOnlyFile; import com.condation.cms.api.extensions.http.HttpHandler; +import com.condation.cms.api.utils.PathUtil; import com.condation.cms.api.utils.RequestUtil; +import com.condation.cms.filesystem.metadata.AbstractMetaData; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.server.AbstractMetaDataConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.Callback; @@ -58,17 +64,31 @@ public boolean handle(Request request, Response response, Callback callback) thr if (uri.startsWith("/")) { uri = uri.substring(1); } + final ReadOnlyFile contentBase = db.getReadOnlyFileSystem().contentBase(); - var file = db.getReadOnlyFileSystem().contentBase().resolve(uri); + var file = contentBase.resolve(uri); if (!file.exists()) { Response.writeError(request, response, callback, 404); return true; } - List children = file.children().stream().map(child -> new NavNode(child.getFileName())).toList(); + var filePath = PathUtil.toRelativeFile(file, contentBase); + if (!db.getContent().isVisible(filePath)) { + Response.writeError(request, response, callback, 403); + return true; + } + + final ContentNode contentNode = db.getContent().byUri(filePath).get(); + + List children = new ArrayList<>(); + db.getContent().listDirectories(file, "").stream() + .filter(child -> AbstractMetaData.isVisible(child)) + .map(child -> new NavNode(child.uri())) + .forEach(children::add); + - NavNode node = new NavNode(file.getFileName(), children); + NavNode node = new NavNode(contentNode.uri(), children); response.getHeaders().add(HttpHeader.CONTENT_TYPE, "application/json; charset=utf-8"); Content.Sink.write(response, true, GSON.toJson(node), callback); From b3f60878604edc168ddcfc3bcf05f36824ba2d78 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Thu, 21 Nov 2024 16:08:18 +0100 Subject: [PATCH 10/11] update navigation api, add links to content and frontend --- .../cms/content/ContentResolver.java | 3 + .../system/handlers/v1/ContentHandler.java | 12 ++- .../system/handlers/v1/NavigationHandler.java | 50 ++++++++--- .../modules/system/helpers/NodeHelper.java | 84 +++++++++++++++++++ 4 files changed, 138 insertions(+), 11 deletions(-) create mode 100644 modules/system-modules/src/main/java/com/condation/cms/modules/system/helpers/NodeHelper.java diff --git a/cms-content/src/main/java/com/condation/cms/content/ContentResolver.java b/cms-content/src/main/java/com/condation/cms/content/ContentResolver.java index 3aa52b032..09a28db8c 100644 --- a/cms-content/src/main/java/com/condation/cms/content/ContentResolver.java +++ b/cms-content/src/main/java/com/condation/cms/content/ContentResolver.java @@ -52,6 +52,9 @@ public class ContentResolver { private final DB db; public Optional getStaticContent (String uri) { + if (uri.endsWith(".md")) { + return Optional.empty(); + } if (uri.startsWith("/")) { uri = uri.substring(1); } diff --git a/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/ContentHandler.java b/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/ContentHandler.java index cec8f7ca8..5ad9310c0 100644 --- a/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/ContentHandler.java +++ b/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/ContentHandler.java @@ -22,14 +22,20 @@ * #L% */ +import com.condation.cms.api.configuration.configs.SiteConfiguration; import com.condation.cms.api.db.ContentNode; import com.condation.cms.api.db.DB; import com.condation.cms.api.db.cms.ReadOnlyFile; import com.condation.cms.api.extensions.http.HttpHandler; +import com.condation.cms.api.feature.features.ConfigurationFeature; +import com.condation.cms.api.request.RequestContext; import com.condation.cms.api.utils.PathUtil; import com.condation.cms.api.utils.RequestUtil; +import com.condation.cms.modules.system.helpers.NodeHelper; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.io.Content; @@ -66,8 +72,12 @@ public boolean handle(Request request, Response response, Callback callback) thr return true; } + final ContentNode node = resolved.get(); + final Map data = new HashMap<>(node.data()); + data.put("_links", NodeHelper.getLinks(node, request)); + response.getHeaders().add(HttpHeader.CONTENT_TYPE, "application/json; charset=utf-8"); - Content.Sink.write(response, true, GSON.toJson(resolved.get().data()), callback); + Content.Sink.write(response, true, GSON.toJson(data), callback); return true; } diff --git a/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/NavigationHandler.java b/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/NavigationHandler.java index eab7113ef..5b241ac92 100644 --- a/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/NavigationHandler.java +++ b/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/NavigationHandler.java @@ -29,15 +29,16 @@ import com.condation.cms.api.utils.PathUtil; import com.condation.cms.api.utils.RequestUtil; import com.condation.cms.filesystem.metadata.AbstractMetaData; +import com.condation.cms.modules.system.helpers.NodeHelper; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.io.Content; -import org.eclipse.jetty.server.AbstractMetaDataConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.Callback; @@ -74,21 +75,50 @@ public boolean handle(Request request, Response response, Callback callback) thr } var filePath = PathUtil.toRelativeFile(file, contentBase); - if (!db.getContent().isVisible(filePath)) { + + /*if (!db.getContent().isVisible(filePath)) { Response.writeError(request, response, callback, 403); return true; - } + }*/ - final ContentNode contentNode = db.getContent().byUri(filePath).get(); + final Optional contentNode = db.getContent().byUri(filePath); List children = new ArrayList<>(); db.getContent().listDirectories(file, "").stream() .filter(child -> AbstractMetaData.isVisible(child)) - .map(child -> new NavNode(child.uri())) - .forEach(children::add); + .map(child -> new NavNode( + NodeHelper.getPath(child), + NodeHelper.getLinks(child, request)) + ).forEach(children::add); + + final String nodeUri = NodeHelper.getPath(uri); + db.getContent().listContent(file, "").stream() + .filter(child -> AbstractMetaData.isVisible(child)) + .filter(child -> + !NodeHelper.getPath(child).equals(nodeUri) + ) + .map(child -> new NavNode( + NodeHelper.getPath(child), + NodeHelper.getLinks(child, request)) + ).forEach(children::add); + + children.sort((node1, node2) -> node1.path.compareTo(node2.path)); + NavNode node; + if (contentNode.isPresent()) { + node = new NavNode( + NodeHelper.getPath(contentNode.get()), + NodeHelper.getLinks(contentNode.get(), request), + children + ); + } else { + node = new NavNode( + "/" + uri, + Collections.emptyMap(), + children + ); + } - NavNode node = new NavNode(contentNode.uri(), children); response.getHeaders().add(HttpHeader.CONTENT_TYPE, "application/json; charset=utf-8"); Content.Sink.write(response, true, GSON.toJson(node), callback); @@ -96,9 +126,9 @@ public boolean handle(Request request, Response response, Callback callback) thr return true; } - private static record NavNode (String path, List children) { - public NavNode (String path) { - this(path, Collections.emptyList()); + private static record NavNode (String path, Map _links, List children) { + public NavNode (String path, Map _links) { + this(path, _links, Collections.emptyList()); } }; } diff --git a/modules/system-modules/src/main/java/com/condation/cms/modules/system/helpers/NodeHelper.java b/modules/system-modules/src/main/java/com/condation/cms/modules/system/helpers/NodeHelper.java new file mode 100644 index 000000000..db464b6ae --- /dev/null +++ b/modules/system-modules/src/main/java/com/condation/cms/modules/system/helpers/NodeHelper.java @@ -0,0 +1,84 @@ +package com.condation.cms.modules.system.helpers; + +/*- + * #%L + * cms-system-modules + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.api.configuration.configs.SiteConfiguration; +import com.condation.cms.api.db.ContentNode; +import com.condation.cms.api.feature.features.ConfigurationFeature; +import com.condation.cms.api.request.RequestContext; +import java.util.Map; +import org.eclipse.jetty.server.Request; + +/** + * + * @author t.marx + */ +public final class NodeHelper { + + private NodeHelper() {} + + public static Map getLinks (ContentNode node, Request request) { + return getLinks(node.uri(), request); + } + + public static Map getLinks (String nodeUri, Request request) { + + var requestContext = (RequestContext) request.getAttribute("_requestContext"); + var siteProperties = requestContext.get(ConfigurationFeature.class).configuration().get(SiteConfiguration.class).siteProperties(); + + var contextPath = siteProperties.contextPath(); + if (!contextPath.endsWith("/")) { + contextPath += "/"; + } + + if (nodeUri.endsWith("index.md")) { + nodeUri = nodeUri.replaceFirst("index.md", ""); + } + + if (nodeUri.endsWith(".md")) { + nodeUri = nodeUri.substring(0, nodeUri.length() - 3); + } + + return Map.of( + "_self", "%s%s".formatted(contextPath, nodeUri), + "_content", "%sapi/v1/content/%s".formatted(contextPath, nodeUri) + ); + } + + public static String getPath (ContentNode node) { + return getPath(node.uri()); + } + + public static String getPath (String uri) { + if (uri.endsWith("index.md")) { + uri = uri.replaceFirst("index.md", ""); + } + if (!uri.startsWith("/")) { + uri = "/" + uri; + } + if (uri.endsWith(".md")) { + uri = uri.substring(0, uri.length() - 3); + } + return uri; + } +} From 193082edab47aeb4073d2a43754a7816917d9efb Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Thu, 28 Nov 2024 14:09:35 +0100 Subject: [PATCH 11/11] disable the system api endpoints until it is better specified --- .../condation/cms/modules/system/handlers/ApiEndpoints.java | 3 +-- .../cms/modules/system/handlers/v1/ContentHandler.java | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/ApiEndpoints.java b/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/ApiEndpoints.java index f05a952f8..816f2cf45 100644 --- a/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/ApiEndpoints.java +++ b/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/ApiEndpoints.java @@ -27,14 +27,13 @@ import com.condation.cms.api.extensions.http.PathMapping; import com.condation.cms.api.feature.features.DBFeature; import com.condation.cms.modules.system.handlers.v1.NavigationHandler; -import com.condation.modules.api.annotation.Extension; import org.eclipse.jetty.http.pathmap.PathSpec; /** * * @author thmar */ -@Extension(APIHandlerExtensionPoint.class) +//@Extension(APIHandlerExtensionPoint.class) public class ApiEndpoints extends APIHandlerExtensionPoint { @Override diff --git a/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/ContentHandler.java b/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/ContentHandler.java index 5ad9310c0..c82d98bc4 100644 --- a/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/ContentHandler.java +++ b/modules/system-modules/src/main/java/com/condation/cms/modules/system/handlers/v1/ContentHandler.java @@ -22,13 +22,10 @@ * #L% */ -import com.condation.cms.api.configuration.configs.SiteConfiguration; import com.condation.cms.api.db.ContentNode; import com.condation.cms.api.db.DB; import com.condation.cms.api.db.cms.ReadOnlyFile; import com.condation.cms.api.extensions.http.HttpHandler; -import com.condation.cms.api.feature.features.ConfigurationFeature; -import com.condation.cms.api.request.RequestContext; import com.condation.cms.api.utils.PathUtil; import com.condation.cms.api.utils.RequestUtil; import com.condation.cms.modules.system.helpers.NodeHelper;