+++ /dev/null
-<?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>
Service-Component:\
-OSGI-INF/pkgServletContext.xml,\
-OSGI-INF/pkgServlet.xml,\
OSGI-INF/statusHandler.xml,\
+++ /dev/null
-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());
- }
- }
-
-}
--- /dev/null
+<?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>
Bundle-Activator: org.argeo.cms.internal.osgi.CmsActivator
Import-Package: \
+org.osgi.framework.namespace,\
*
Export-Package:\
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,\
*/
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"), //
;
--- /dev/null
+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);
+ }
+ }
+
+}