You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by kw...@apache.org on 2021/12/14 10:49:46 UTC

[sling-org-apache-sling-jcr-contentloader] branch master updated: SLING-10988 provide web console plugin for initial content (#8)

This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git


The following commit(s) were added to refs/heads/master by this push:
     new 71f25e6  SLING-10988 provide web console plugin for initial content (#8)
71f25e6 is described below

commit 71f25e644864567ad04321feb10bfc60a95d3d4e
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Tue Dec 14 11:49:42 2021 +0100

    SLING-10988 provide web console plugin for initial content (#8)
---
 pom.xml                                            |  19 ++-
 .../apache/sling/jcr/contentloader/PathEntry.java  |   4 +
 .../contentloader/hc/BundleContentLoadedCheck.java |   7 +-
 .../internal/BundleContentLoaderListener.java      |   9 +-
 .../internal/ContentLoaderWebConsolePlugin.java    | 181 +++++++++++++++++++++
 src/main/resources/res/ui/main.css                 |  27 +++
 6 files changed, 234 insertions(+), 13 deletions(-)

diff --git a/pom.xml b/pom.xml
index be5f0da..af14a2d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -236,7 +236,18 @@
             <version>2.0.0</version>
             <scope>provided</scope>
         </dependency>
-
+        <!-- for Web Console plugin -->
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.11.0</version>
+            <scope>provided</scope>
+        </dependency>
         <!-- Apache Felix -->
         <dependency>
             <groupId>org.apache.felix</groupId>
@@ -271,12 +282,6 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.api</artifactId>
-            <version>2.11.0</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.testing.osgi-mock</artifactId>
             <version>2.3.2</version>
             <scope>test</scope>
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/PathEntry.java b/src/main/java/org/apache/sling/jcr/contentloader/PathEntry.java
index 37ee9e6..e6a4a81 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/PathEntry.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/PathEntry.java
@@ -388,6 +388,10 @@ public class PathEntry extends ImportOptions {
         return this.ignoreContentReaders.contains(extension);
     }
 
+    public Set<String> getIgnoredContentReaders() {
+        return new HashSet<>(ignoreContentReaders);
+    }
+
     public String getTarget() {
         return target;
     }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/hc/BundleContentLoadedCheck.java b/src/main/java/org/apache/sling/jcr/contentloader/hc/BundleContentLoadedCheck.java
index b032bc6..f8c8d46 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/hc/BundleContentLoadedCheck.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/hc/BundleContentLoadedCheck.java
@@ -98,6 +98,9 @@ public class BundleContentLoadedCheck implements HealthCheck {
     @Reference
     private SlingRepository repository;
 
+    @Reference
+    private BundleHelper bundleHelper;
+
     @Activate
     public void activate(BundleContext bundleContext, Config config) {
         this.bundleContext = bundleContext;
@@ -123,9 +126,7 @@ public class BundleContentLoadedCheck implements HealthCheck {
         Session metadataSession = null;
         try {
             metadataSession = repository.loginService(null, null);
-            
-            BundleHelper bundleHelper = new BundleContentLoaderListener();
-            
+           
             for (Bundle bundle : bundles) {
                 String bundleSymbolicName = bundle.getSymbolicName();
                 if (!includesRegex.matcher(bundleSymbolicName).matches()) {
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoaderListener.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoaderListener.java
index 4cc37cb..1df747b 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoaderListener.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoaderListener.java
@@ -59,15 +59,15 @@ import org.slf4j.LoggerFactory;
  * </ul>
  *
  */
-@Component(service = {}, property = { Constants.SERVICE_VENDOR + "=The Apache Software Foundation",
+@Component(service = {BundleHelper.class}, property = { Constants.SERVICE_VENDOR + "=The Apache Software Foundation",
         Constants.SERVICE_DESCRIPTION
-                + "=Apache Sling Content Loader Implementation" }, configurationPolicy = ConfigurationPolicy.OPTIONAL)
+                + "=Apache Sling Content Loader Implementation" }, immediate = true, configurationPolicy = ConfigurationPolicy.OPTIONAL)
 @Designate(ocd = BundleContentLoaderConfiguration.class, factory = false)
 public class BundleContentLoaderListener implements SynchronousBundleListener, BundleHelper {
 
     public static final String PROPERTY_CONTENT_LOADED = "content-loaded";
     public static final String PROPERTY_CONTENT_LOADED_AT = "content-load-time";
-    private static final String PROPERTY_CONTENT_LOADED_BY = "content-loaded-by";
+    protected static final String PROPERTY_CONTENT_LOADED_BY = "content-loaded-by";
     private static final String PROPERTY_CONTENT_UNLOADED_AT = "content-unload-time";
     private static final String PROPERTY_CONTENT_UNLOADED_BY = "content-unloaded-by";
     public static final String PROPERTY_UNINSTALL_PATHS = "uninstall-paths";
@@ -370,6 +370,9 @@ public class BundleContentLoaderListener implements SynchronousBundleListener, B
         } else {
             info.put(PROPERTY_CONTENT_LOADED, false);
         }
+        if (bcNode.hasProperty(PROPERTY_CONTENT_LOADED_BY)) {
+            info.put(PROPERTY_CONTENT_LOADED_BY, bcNode.getProperty(PROPERTY_CONTENT_LOADED_BY).getString());
+        }
         if (bcNode.hasProperty(PROPERTY_UNINSTALL_PATHS)) {
             final Value[] values = bcNode.getProperty(PROPERTY_UNINSTALL_PATHS).getValues();
             final String[] s = new String[values.length];
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderWebConsolePlugin.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderWebConsolePlugin.java
new file mode 100644
index 0000000..b41dc60
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderWebConsolePlugin.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.jcr.contentloader.internal;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Map;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.servlet.GenericServlet;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.apache.sling.api.request.ResponseUtil;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.jcr.contentloader.PathEntry;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+@Component(service=javax.servlet.Servlet.class,
+    property = {
+        Constants.SERVICE_VENDOR + "=The Apache Software Foundation",
+        Constants.SERVICE_DESCRIPTION + "=Apache Sling JCR Content Loader Web Console Plugin",
+        "felix.webconsole.label=" + ContentLoaderWebConsolePlugin.LABEL,
+        "felix.webconsole.title=JCR Content Loader",
+        "felix.webconsole.category=Sling",
+        "felix.webconsole.css=" + ContentLoaderWebConsolePlugin.RES_LOC + "main.css"
+    })
+@SuppressWarnings("serial")
+public class ContentLoaderWebConsolePlugin extends GenericServlet {
+
+    public static final String LABEL = "jcr-content-loader";
+    protected static final String RES_LOC = LABEL + "/res/ui/";
+
+    @Reference
+    SlingRepository repository;
+
+    @Reference
+    BundleHelper bundleHelper;
+
+    BundleContext context;
+
+    @Activate()
+    private void activate(BundleContext context) {
+        this.context = context;
+    }
+
+    @Override
+    public void service(final ServletRequest req, final ServletResponse res)
+            throws IOException {
+        Session session = null;
+        PrintWriter pw = res.getWriter();
+        try {
+            session = repository.loginService(null, null);
+            pw.print("<p class='statline ui-state-highlight'>Apache Sling JCR Content Loader");
+            pw.print("</p>");
+            pw.println("<table class='nicetable'><thead>");
+            pw.println("<tr><th>Bundle</th><th>Path Entries</th><th>Content Loaded Successfully?</th><th>Uninstall Paths (format: JCR workspace:path)</th></tr>");
+            pw.println("</thead><tbody>");
+            int bundleNo = 1;
+            for (final Bundle bundle : context.getBundles()) {
+                String contentHeader = bundle.getHeaders().get(PathEntry.CONTENT_HEADER);
+                if (contentHeader != null) {
+                    printBundleInfoTableRow(pw, req, session, bundle, (bundleNo++ % 2 == 0));
+                }
+            }
+            pw.println("</tbody></table>");
+        } catch (RepositoryException e) {
+            pw.println("Error accessing the underlying repository");
+            e.printStackTrace(pw);
+        } finally {
+            if (session != null) {
+                session.logout();
+            }
+        }
+    }
+
+    void printBundleInfoTableRow(PrintWriter pw, final ServletRequest req, Session session, Bundle bundle, boolean isEven) throws RepositoryException {
+        Map<String, Object> contentInfoMap = bundleHelper.getBundleContentInfo(session, bundle, false);
+        // release lock as early as possible
+        bundleHelper.unlockBundleContentInfo(session, bundle, false, null);
+        
+        String[] uninstallPaths = (String[])contentInfoMap.get(BundleContentLoaderListener.PROPERTY_UNINSTALL_PATHS);
+        final String uninstallPathsString;
+        if (uninstallPaths == null) {
+            uninstallPathsString = "-";
+        } else {
+            uninstallPathsString = Arrays.stream(uninstallPaths).map(ResponseUtil::escapeXml).collect(Collectors.joining("<br/>"));
+        }
+        Object loadedDate = contentInfoMap.get(BundleContentLoaderListener.PROPERTY_CONTENT_LOADED_AT);
+        final String loadedDetails;
+        if (!(loadedDate instanceof Calendar)) {
+            loadedDetails = "?";
+        } else {
+            Calendar calendar = Calendar.class.cast(loadedDate);
+            String formatterDate = DateTimeFormatter.ISO_ZONED_DATE_TIME.withZone(calendar.getTimeZone().toZoneId()).withLocale(req.getLocale())
+                    .format(calendar.toInstant());
+            String loadedBy = String.valueOf(contentInfoMap.get(BundleContentLoaderListener.PROPERTY_CONTENT_LOADED_BY));
+            loadedDetails = String.format("%s<br/>by Sling ID %s", formatterDate, ResponseUtil.escapeXml(loadedBy));
+        }
+        // https://felix.apache.org/documentation/subprojects/apache-felix-web-console/extending-the-apache-felix-web-console/providing-web-console-plugins.html
+        String bundleLink = req.getAttribute("felix.webconsole.appRoot") + "/bundles/" + bundle.getBundleId();
+        String pathEntriesString = StreamSupport.stream(Spliterators.spliteratorUnknownSize(PathEntry.getContentPaths(bundle), Spliterator.ORDERED), false)
+                .map(ContentLoaderWebConsolePlugin::printPathEntryTable).collect(Collectors.joining("\n"));
+        String trClass = (isEven ? "even" : "odd") + " ui-state-default";
+        pw.printf("<tr class='%s'><td><a href=\"%s\">%s (%d)</a></td><td>%s</td><td>%s<br/><br/>(%s)</td><td>%s</td></tr>",
+                trClass,
+                bundleLink,
+                ResponseUtil.escapeXml(bundle.getSymbolicName()), bundle.getBundleId(),
+                pathEntriesString,
+                PropertiesUtil.toBoolean(contentInfoMap.get(BundleContentLoaderListener.PROPERTY_CONTENT_LOADED), false),
+                loadedDetails,
+                uninstallPathsString);
+    }
+
+    static String printPathEntryTable(PathEntry entry) {
+        StringBuilder sb = new StringBuilder();
+        int row = 1;
+        sb.append("<table class='nicetable nested'><thead>");
+        sb.append("<tr><th>Path</th><th>").append(ResponseUtil.escapeXml(entry.getPath())).append("</th><tr>");
+        sb.append("</thead><tbody>");
+        printPathEntryTableRow(sb, "Target Path", ResponseUtil.escapeXml(entry.getTarget()), row++);
+        // most important directives
+        printPathEntryTableRow(sb, "Overwrite", Boolean.toString(entry.isOverwrite()), row++);
+        printPathEntryTableRow(sb, "Uninstall", Boolean.toString(entry.isUninstall()), row++);
+        printPathEntryTableRow(sb, "Ignored Content Readers", ResponseUtil.escapeXml(String.join(", ", entry.getIgnoredContentReaders())), row);
+        sb.append("</tbody></table>");
+        return sb.toString();
+    }
+
+    static void printPathEntryTableRow(StringBuilder sb, String name, String value, int i) {
+        String trClass = (i % 2 == 0 ? "even" : "odd") + " ui-state-default";
+        sb.append("<tr class='").append(trClass).append("'><td>").append(name).append("</td><td>").append(value).append("</td></tr>");
+    }
+
+    /**
+     * Method to retrieve static resources from this bundle.
+     */
+    @SuppressWarnings("unused")
+    private URL getResource(final String path) {
+        if (path.startsWith("/" + RES_LOC)) {
+            // strip label
+            int index = path.indexOf('/', 1);
+            if (index <= 0) {
+                throw new IllegalStateException("The relativeResourcePrefix must contain at least one '/'");
+            }
+            return this.getClass().getResource(path.substring(index));
+        }
+        return null;
+    }
+}
diff --git a/src/main/resources/res/ui/main.css b/src/main/resources/res/ui/main.css
new file mode 100644
index 0000000..1a47641
--- /dev/null
+++ b/src/main/resources/res/ui/main.css
@@ -0,0 +1,27 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+.nested .odd {
+  background-color: white !important;
+}
+
+.nested th:nth-child(1) {
+  width: 1%;
+  white-space: nowrap;
+}