]> git.argeo.org Git - lgpl/argeo-commons.git/commitdiff
Use HTTP handler for package resources publication
authorMathieu Baudier <mbaudier@argeo.org>
Mon, 23 Sep 2024 13:08:49 +0000 (15:08 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Mon, 23 Sep 2024 13:08:49 +0000 (15:08 +0200)
org.argeo.cms.ee/OSGI-INF/pkgServlet.xml [deleted file]
org.argeo.cms.ee/bnd.bnd
org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/PkgServlet.java [deleted file]
org.argeo.cms/OSGI-INF/pkgHttpHandler.xml [new file with mode: 0644]
org.argeo.cms/bnd.bnd
org.argeo.cms/src/org/argeo/cms/http/HttpStatus.java
org.argeo.cms/src/org/argeo/cms/internal/http/PkgHttpHandler.java [new file with mode: 0644]

diff --git a/org.argeo.cms.ee/OSGI-INF/pkgServlet.xml b/org.argeo.cms.ee/OSGI-INF/pkgServlet.xml
deleted file mode 100644 (file)
index 05978e6..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.cms.pkgServlet">
-   <implementation class="org.argeo.cms.servlet.internal.PkgServlet"/>
-   <service>
-      <provide interface="jakarta.servlet.Servlet"/>
-   </service>
-   <property name="osgi.http.whiteboard.servlet.pattern" type="String" value="/*"/>
-   <property name="osgi.http.whiteboard.context.select" type="String" value="(osgi.http.whiteboard.context.name=pkgServletContext)"/>
-</scr:component>
index 592abffdb45190c0581f543fabf9ff9acd5ea08e..83df366bbd9b5abd1f262f096cd4ca150bf33c87 100644 (file)
@@ -10,6 +10,4 @@ org.argeo.cms.osgi,\
 
 
 Service-Component:\
-OSGI-INF/pkgServletContext.xml,\
-OSGI-INF/pkgServlet.xml,\
 OSGI-INF/statusHandler.xml,\
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/PkgServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/PkgServlet.java
deleted file mode 100644 (file)
index 07e3ccc..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-package org.argeo.cms.servlet.internal;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.FileNameMap;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.Collection;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServlet;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
-import org.argeo.cms.osgi.FilterRequirement;
-import org.argeo.cms.osgi.PublishNamespace;
-import org.argeo.cms.util.StreamUtils;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.Version;
-import org.osgi.framework.VersionRange;
-import org.osgi.framework.namespace.PackageNamespace;
-import org.osgi.framework.wiring.BundleCapability;
-import org.osgi.framework.wiring.BundleWiring;
-import org.osgi.framework.wiring.FrameworkWiring;
-import org.osgi.resource.Requirement;
-
-/**
- * Publishes client-side web resources (JavaScript, HTML, CSS, images, etc.)
- * from the OSGi runtime.
- */
-public class PkgServlet extends HttpServlet {
-       private static final long serialVersionUID = 7660824185145214324L;
-
-       private static FileNameMap fileNameMap = URLConnection.getFileNameMap();
-
-       private BundleContext bundleContext = FrameworkUtil.getBundle(PkgServlet.class).getBundleContext();
-
-       @Override
-       protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-               String pathInfo = req.getPathInfo();
-
-               String pkg, versionStr, file;
-               String[] parts = pathInfo.split("/");
-               // first is always empty
-               if (parts.length == 4) {
-                       pkg = parts[1];
-                       versionStr = parts[2];
-                       file = parts[3];
-               } else if (parts.length == 3) {
-                       pkg = parts[1];
-                       versionStr = null;
-                       file = parts[2];
-               } else {
-                       throw new IllegalArgumentException("Unsupported path length " + pathInfo);
-               }
-
-               // content type
-               String contentType = fileNameMap.getContentTypeFor(file);
-               resp.setContentType(contentType);
-
-               FrameworkWiring frameworkWiring = bundleContext.getBundle(0).adapt(FrameworkWiring.class);
-               String filter;
-               if (versionStr == null) {
-                       filter = "(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")";
-               } else {
-                       if (versionStr.startsWith("[") || versionStr.startsWith("(")) {// range
-                               VersionRange versionRange = new VersionRange(versionStr);
-                               filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")"
-                                               + versionRange.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE) + ")";
-
-                       } else {
-                               Version version = new Version(versionStr);
-                               filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")("
-                                               + PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=" + version + "))";
-                       }
-               }
-               Requirement requirement = new FilterRequirement(PackageNamespace.PACKAGE_NAMESPACE, filter);
-               Collection<BundleCapability> packages = frameworkWiring.findProviders(requirement);
-               if (packages.isEmpty()) {
-                       resp.sendError(404);
-                       return;
-               }
-
-               // TODO verify that it works with multiple versions
-               SortedMap<Version, BundleCapability> sorted = new TreeMap<>();
-               for (BundleCapability capability : packages) {
-                       sorted.put(capability.getRevision().getVersion(), capability);
-               }
-
-               Bundle bundle = sorted.get(sorted.firstKey()).getRevision().getBundle();
-               String entryPath = '/' + pkg.replace('.', '/') + '/' + file;
-               URL internalURL = bundle.getResource(entryPath);
-               if (internalURL == null) {
-                       resp.sendError(404);
-                       return;
-               }
-
-               // Resource found, we now check whether it can be published
-               boolean publish = false;
-               BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
-               capabilities: for (BundleCapability bundleCapability : bundleWiring
-                               .getCapabilities(PublishNamespace.CMS_PUBLISH_NAMESPACE)) {
-                       Object publishedPkg = bundleCapability.getAttributes().get(PublishNamespace.PKG);
-                       if (publishedPkg != null) {
-                               if (publishedPkg.equals("*") || publishedPkg.equals(pkg)) {
-                                       Object publishedFile = bundleCapability.getAttributes().get(PublishNamespace.FILE);
-                                       if (publishedFile == null) {
-                                               publish = true;
-                                               break capabilities;
-                                       } else {
-                                               String[] publishedFiles = publishedFile.toString().split(",");
-                                               for (String pattern : publishedFiles) {
-                                                       if (pattern.startsWith("*.")) {
-                                                               String ext = pattern.substring(1);
-                                                               if (file.endsWith(ext)) {
-                                                                       publish = true;
-                                                                       break capabilities;
-                                                               }
-                                                       } else {
-                                                               if (publishedFile.equals(file)) {
-                                                                       publish = true;
-                                                                       break capabilities;
-                                                               }
-                                                       }
-                                               }
-                                       }
-                               }
-                       }
-               }
-
-               if (!publish) {
-                       resp.sendError(404);
-                       return;
-               }
-
-               try (InputStream in = internalURL.openStream()) {
-                       StreamUtils.copy(in, resp.getOutputStream());
-               }
-       }
-
-}
diff --git a/org.argeo.cms/OSGI-INF/pkgHttpHandler.xml b/org.argeo.cms/OSGI-INF/pkgHttpHandler.xml
new file mode 100644 (file)
index 0000000..4afd9a6
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="org.argeo.cms.pkgServlet">
+   <implementation class="org.argeo.cms.internal.http.PkgHttpHandler"/>
+   <service>
+      <provide interface="com.sun.net.httpserver.HttpHandler"/>
+   </service>
+   <property name="context.path" type="String" value="/pkg/" />
+   <property name="context.public" type="String" value="true" />
+</scr:component>
index bcba7939e166a3d7a0bfe2cb55fb33c84b93adf7..3fd92c226b6f324174bc4888677d43fcdf1d7f3a 100644 (file)
@@ -1,6 +1,7 @@
 Bundle-Activator: org.argeo.cms.internal.osgi.CmsActivator
 
 Import-Package: \
+org.osgi.framework.namespace,\
 *
 
 Export-Package:\
@@ -15,8 +16,9 @@ OSGI-INF/transactionManager.xml,\
 OSGI-INF/cmsUserAdmin.xml,\
 OSGI-INF/cmsUserManager.xml,\
 OSGI-INF/cmsContentRepository.xml,\
-OSGI-INF/cmsAcrHttpHandler.xml,\
 OSGI-INF/cmsDeployment.xml,\
 OSGI-INF/cmsContext.xml,\
 OSGI-INF/cmsFileSystemProvider.xml,\
+OSGI-INF/cmsAcrHttpHandler.xml,\
+OSGI-INF/pkgHttpHandler.xml,\
 
index 3b9a47a38b1e2750b12bbc740a1def591a4d0f8a..f899bdcee6e339c6a09d852646fa41553d3b7ab6 100644 (file)
@@ -7,15 +7,23 @@ package org.argeo.cms.http;
  */
 public enum HttpStatus {
        // Successful responses (200–299)
+       /** 200 */
        OK(200, "OK"), //
+       /** 204 */
        NO_CONTENT(204, "No Content"), //
+       /** 207 */
        MULTI_STATUS(207, "Multi-Status"), // WebDav
        // Client error responses (400–499)
+       /** 401 */
        UNAUTHORIZED(401, "Unauthorized"), //
+       /** 403 */
        FORBIDDEN(403, "Forbidden"), //
+       /** 404 */
        NOT_FOUND(404, "Not Found"), //
        // Server error responses (500-599)
+       /** 500 */
        INTERNAL_SERVER_ERROR(500, "Internal Server Error"), //
+       /** 501 */
        NOT_IMPLEMENTED(501, "Not Implemented"), //
        ;
 
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/PkgHttpHandler.java b/org.argeo.cms/src/org/argeo/cms/internal/http/PkgHttpHandler.java
new file mode 100644 (file)
index 0000000..a737ab6
--- /dev/null
@@ -0,0 +1,163 @@
+package org.argeo.cms.internal.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.FileNameMap;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.argeo.cms.http.HttpHeader;
+import org.argeo.cms.http.HttpStatus;
+import org.argeo.cms.http.server.HttpServerUtils;
+import org.argeo.cms.osgi.FilterRequirement;
+import org.argeo.cms.osgi.PublishNamespace;
+import org.argeo.cms.util.StreamUtils;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Version;
+import org.osgi.framework.VersionRange;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.framework.wiring.FrameworkWiring;
+import org.osgi.resource.Requirement;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+
+/**
+ * Publishes client-side web resources (JavaScript, HTML, CSS, images, etc.)
+ * from the OSGi runtime.
+ */
+public class PkgHttpHandler implements HttpHandler {
+       // TODO make it configurable?
+       private FileNameMap fileNameMap = URLConnection.getFileNameMap();
+
+       private FrameworkWiring frameworkWiring = null;
+
+       public void start(BundleContext bundleContext) {
+               this.frameworkWiring = bundleContext.getBundle(0).adapt(FrameworkWiring.class);
+       }
+
+       public void stop() {
+               this.frameworkWiring = null;
+       }
+
+       @Override
+       public void handle(HttpExchange exchange) throws IOException {
+               // String pathInfo = req.getPathInfo();
+               String pathInfo = HttpServerUtils.subPath(exchange);
+
+               String pkg, versionStr, file;
+               String[] parts = pathInfo.split("/");
+               // first is always empty
+               if (parts.length == 4) {
+                       pkg = parts[1];
+                       versionStr = parts[2];
+                       file = parts[3];
+               } else if (parts.length == 3) {
+                       pkg = parts[1];
+                       versionStr = null;
+                       file = parts[2];
+               } else {
+                       throw new IllegalArgumentException("Unsupported path length " + pathInfo);
+               }
+
+               // content type
+               String contentType = fileNameMap.getContentTypeFor(file);
+               exchange.getResponseHeaders().put(HttpHeader.CONTENT_TYPE.getHeaderName(),
+                               Collections.singletonList(contentType));
+//             resp.setContentType(contentType);
+
+               String filter;
+               if (versionStr == null) {
+                       filter = "(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")";
+               } else {
+                       if (versionStr.startsWith("[") || versionStr.startsWith("(")) {// range
+                               VersionRange versionRange = new VersionRange(versionStr);
+                               filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")"
+                                               + versionRange.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE) + ")";
+
+                       } else {
+                               Version version = new Version(versionStr);
+                               filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")("
+                                               + PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=" + version + "))";
+                       }
+               }
+               Requirement requirement = new FilterRequirement(PackageNamespace.PACKAGE_NAMESPACE, filter);
+               Collection<BundleCapability> packages = frameworkWiring.findProviders(requirement);
+               if (packages.isEmpty()) {
+//                     resp.sendError(404);
+                       exchange.sendResponseHeaders(HttpStatus.NOT_FOUND.getCode(), -1);
+                       return;
+               }
+
+               // TODO verify that it works with multiple versions
+               SortedMap<Version, BundleCapability> sorted = new TreeMap<>();
+               for (BundleCapability capability : packages) {
+                       sorted.put(capability.getRevision().getVersion(), capability);
+               }
+
+               Bundle bundle = sorted.get(sorted.firstKey()).getRevision().getBundle();
+               String entryPath = '/' + pkg.replace('.', '/') + '/' + file;
+               URL internalURL = bundle.getResource(entryPath);
+               if (internalURL == null) {
+//                     resp.sendError(404);
+                       exchange.sendResponseHeaders(HttpStatus.NOT_FOUND.getCode(), -1);
+                       return;
+               }
+
+               // Resource found, we now check whether it can be published
+               boolean publish = false;
+               BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
+               capabilities: for (BundleCapability bundleCapability : bundleWiring
+                               .getCapabilities(PublishNamespace.CMS_PUBLISH_NAMESPACE)) {
+                       Object publishedPkg = bundleCapability.getAttributes().get(PublishNamespace.PKG);
+                       if (publishedPkg != null) {
+                               if (publishedPkg.equals("*") || publishedPkg.equals(pkg)) {
+                                       Object publishedFile = bundleCapability.getAttributes().get(PublishNamespace.FILE);
+                                       if (publishedFile == null) {
+                                               publish = true;
+                                               break capabilities;
+                                       } else {
+                                               String[] publishedFiles = publishedFile.toString().split(",");
+                                               for (String pattern : publishedFiles) {
+                                                       if (pattern.startsWith("*.")) {
+                                                               String ext = pattern.substring(1);
+                                                               if (file.endsWith(ext)) {
+                                                                       publish = true;
+                                                                       break capabilities;
+                                                               }
+                                                       } else {
+                                                               if (publishedFile.equals(file)) {
+                                                                       publish = true;
+                                                                       break capabilities;
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               if (!publish) {
+//                     resp.sendError(404);
+                       exchange.sendResponseHeaders(HttpStatus.NOT_FOUND.getCode(), -1);
+                       return;
+               }
+
+               // TODO cache length?
+               exchange.sendResponseHeaders(HttpStatus.OK.getCode(), 0);
+               try (InputStream in = internalURL.openStream(); //
+                               OutputStream out = exchange.getResponseBody();) {
+//                     StreamUtils.copy(in, resp.getOutputStream());
+                       StreamUtils.copy(in, out);
+               }
+       }
+
+}