From: Mathieu Baudier Date: Tue, 20 Jul 2021 11:25:48 +0000 (+0200) Subject: Introduce package servlet. X-Git-Tag: argeo-commons-2.1.103~9 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=8a934d18841f04c32f9bb05acebd8791f8f4f101 Introduce package servlet. --- 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 index 000000000..ff9575486 --- /dev/null +++ b/org.argeo.api/src/org/argeo/api/PublishNamespace.java @@ -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 + } + +} diff --git a/org.argeo.cms/OSGI-INF/filesServlet.xml b/org.argeo.cms/OSGI-INF/filesServlet.xml index 498d09749..ec161faa6 100644 --- a/org.argeo.cms/OSGI-INF/filesServlet.xml +++ b/org.argeo.cms/OSGI-INF/filesServlet.xml @@ -1,5 +1,5 @@ - + diff --git a/org.argeo.cms/OSGI-INF/pkgServlet.xml b/org.argeo.cms/OSGI-INF/pkgServlet.xml new file mode 100644 index 000000000..30e52311c --- /dev/null +++ b/org.argeo.cms/OSGI-INF/pkgServlet.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/org.argeo.cms/OSGI-INF/pkgServletContext.xml b/org.argeo.cms/OSGI-INF/pkgServletContext.xml new file mode 100644 index 000000000..7540a2cdb --- /dev/null +++ b/org.argeo.cms/OSGI-INF/pkgServletContext.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/org.argeo.cms/bnd.bnd b/org.argeo.cms/bnd.bnd index 9ecfb8f1a..5af544206 100644 --- a/org.argeo.cms/bnd.bnd +++ b/org.argeo.cms/bnd.bnd @@ -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 index 000000000..9cbd21c9f --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/PkgServlet.java @@ -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 packages = frameworkWiring.findProviders(requirement); + if (packages.isEmpty()) { + resp.sendError(404); + return; + } + + // TODO verify that it works with multiple versions + SortedMap 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 index 000000000..7cb586310 --- /dev/null +++ b/org.argeo.enterprise/src/org/argeo/osgi/util/FilterRequirement.java @@ -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 getDirectives() { + Map directives = new HashMap<>(); + directives.put(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter); + return directives; + } + + @Override + public Map getAttributes() { + return new HashMap<>(); + } + +}