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;
+}