Introduce package servlet.
authorMathieu Baudier <mbaudier@argeo.org>
Tue, 20 Jul 2021 11:25:48 +0000 (13:25 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Tue, 20 Jul 2021 11:25:48 +0000 (13:25 +0200)
org.argeo.api/src/org/argeo/api/PublishNamespace.java [new file with mode: 0644]
org.argeo.cms/OSGI-INF/filesServlet.xml
org.argeo.cms/OSGI-INF/pkgServlet.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/pkgServletContext.xml [new file with mode: 0644]
org.argeo.cms/bnd.bnd
org.argeo.cms/src/org/argeo/cms/internal/http/PkgServlet.java [new file with mode: 0644]
org.argeo.enterprise/src/org/argeo/osgi/util/FilterRequirement.java [new file with mode: 0644]

diff --git a/org.argeo.api/src/org/argeo/api/PublishNamespace.java b/org.argeo.api/src/org/argeo/api/PublishNamespace.java
new file mode 100644 (file)
index 0000000..ff95754
--- /dev/null
@@ -0,0 +1,16 @@
+package org.argeo.api;
+
+import org.osgi.resource.Namespace;
+
+/** Namespace defining which resources can be published. Typically use to expose icon of scripts to the web. */
+public class PublishNamespace extends Namespace {
+
+       public static final String CMS_PUBLISH_NAMESPACE = "cms.publish";
+       public static final String PKG = "pkg";
+       public static final String FILE = "file";
+
+       private PublishNamespace() {
+               // empty
+       }
+
+}
index 498d09749982022d14d48fe879acfbc97a812227..ec161faa627ee16d8568032b94615dfba27a0729 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.cms">
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.cms.filesServlet">
    <implementation class="org.argeo.cms.internal.http.CmsWebDavServlet"/>
    <service>
       <provide interface="javax.servlet.Servlet"/>
diff --git a/org.argeo.cms/OSGI-INF/pkgServlet.xml b/org.argeo.cms/OSGI-INF/pkgServlet.xml
new file mode 100644 (file)
index 0000000..30e5231
--- /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" name="org.argeo.cms.pkgServlet">
+   <implementation class="org.argeo.cms.internal.http.PkgServlet"/>
+   <service>
+      <provide interface="javax.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>
diff --git a/org.argeo.cms/OSGI-INF/pkgServletContext.xml b/org.argeo.cms/OSGI-INF/pkgServletContext.xml
new file mode 100644 (file)
index 0000000..7540a2c
--- /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="init" deactivate="destroy" name="org.argeo.cms.pkgServletContext">
+   <implementation class="org.argeo.cms.servlet.CmsServletContext"/>
+   <service>
+      <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
+   </service>
+   <property name="osgi.http.whiteboard.context.name" type="String" value="pkgServletContext"/>
+   <property name="osgi.http.whiteboard.context.path" type="String" value="/pkg"/>
+</scr:component>
index 9ecfb8f1aae88a3eaf7f44df2960ee6117eebe27..5af544206695c76471f8b2b2095479196d02da00 100644 (file)
@@ -7,12 +7,15 @@ org.apache.jackrabbit.webdav.server,\
 org.apache.jackrabbit.webdav.jcr,\
 org.apache.commons.httpclient.cookie;resolution:=optional,\
 !com.sun.security.jgss,\
+org.osgi.framework.namespace;version=0.0.0,\
 org.osgi.*;version=0.0.0,\
 org.osgi.service.http.whiteboard,\
 *
 
 Service-Component:\
 OSGI-INF/cmsUserManager.xml,\
+OSGI-INF/pkgServletContext.xml,\
+OSGI-INF/pkgServlet.xml,\
 OSGI-INF/jcrServletContext.xml,\
 OSGI-INF/dataServletContext.xml,\
 OSGI-INF/filesServletContext.xml,\
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/PkgServlet.java b/org.argeo.cms/src/org/argeo/cms/internal/http/PkgServlet.java
new file mode 100644 (file)
index 0000000..9cbd21c
--- /dev/null
@@ -0,0 +1,134 @@
+package org.argeo.cms.internal.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.api.PublishNamespace;
+import org.argeo.osgi.util.FilterRequirement;
+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;
+
+public class PkgServlet extends HttpServlet {
+       private static final long serialVersionUID = 7660824185145214324L;
+
+       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);
+               }
+
+               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()) {
+                       IOUtils.copy(in, resp.getOutputStream());
+               }
+       }
+
+}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/util/FilterRequirement.java b/org.argeo.enterprise/src/org/argeo/osgi/util/FilterRequirement.java
new file mode 100644 (file)
index 0000000..7cb5863
--- /dev/null
@@ -0,0 +1,43 @@
+package org.argeo.osgi.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.resource.Namespace;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+public class FilterRequirement implements Requirement {
+       private String namespace;
+       private String filter;
+       
+       
+
+       public FilterRequirement(String namespace, String filter) {
+               this.namespace = namespace;
+               this.filter = filter;
+       }
+
+       @Override
+       public Resource getResource() {
+               return null;
+       }
+
+       @Override
+       public String getNamespace() {
+               return namespace;
+       }
+
+       @Override
+       public Map<String, String> getDirectives() {
+               Map<String, String> directives = new HashMap<>();
+               directives.put(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter);
+               return directives;
+       }
+
+       @Override
+       public Map<String, Object> getAttributes() {
+               return new HashMap<>();
+       }
+
+}