This is an automated email from the ASF dual-hosted git repository. diru pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git
commit 528118cdfda86493addb4e19a28d3e1e53a8047a Author: Dirk Rudolph <[email protected]> AuthorDate: Mon Jun 7 14:44:31 2021 +0200 add sling sitemap invetory plugin --- sitemap/pom.xml | 6 + .../java/org/apache/sling/sitemap/SitemapInfo.java | 10 + .../sling/sitemap/impl/SitemapServiceImpl.java | 33 ++-- .../apache/sling/sitemap/impl/SitemapServlet.java | 12 +- .../apache/sling/sitemap/impl/SitemapStorage.java | 16 +- .../org/apache/sling/sitemap/impl/SitemapUtil.java | 23 ++- .../impl/console/SitemapInventoryPlugin.java | 205 +++++++++++++++++++++ 7 files changed, 275 insertions(+), 30 deletions(-) diff --git a/sitemap/pom.xml b/sitemap/pom.xml index 711bb03..0302154 100644 --- a/sitemap/pom.xml +++ b/sitemap/pom.xml @@ -166,6 +166,12 @@ <scope>provided</scope> </dependency> <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.inventory</artifactId> + <version>1.0.6</version> + <scope>provided</scope> + </dependency> + <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <scope>provided</scope> diff --git a/sitemap/src/main/java/org/apache/sling/sitemap/SitemapInfo.java b/sitemap/src/main/java/org/apache/sling/sitemap/SitemapInfo.java index da7caaa..6579b23 100644 --- a/sitemap/src/main/java/org/apache/sling/sitemap/SitemapInfo.java +++ b/sitemap/src/main/java/org/apache/sling/sitemap/SitemapInfo.java @@ -19,6 +19,7 @@ package org.apache.sling.sitemap; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.osgi.annotation.versioning.ProviderType; /** @@ -28,6 +29,15 @@ import org.osgi.annotation.versioning.ProviderType; public interface SitemapInfo { /** + * Returns a resource path to the node the sitemap is stored. May return null if the sitemap or sitemap-index is + * served on-demand. + * + * @return + */ + @Nullable + String getStoragePath(); + + /** * Returns the absolute, external url for the sitemap/sitemap-index. * * @return diff --git a/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapServiceImpl.java b/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapServiceImpl.java index 22e6111..aa3703c 100644 --- a/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapServiceImpl.java +++ b/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapServiceImpl.java @@ -25,6 +25,7 @@ import org.apache.sling.sitemap.SitemapInfo; import org.apache.sling.sitemap.SitemapService; import org.apache.sling.sitemap.common.SitemapLinkExternalizer; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.osgi.service.component.annotations.*; import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.Designate; @@ -58,7 +59,7 @@ public class SitemapServiceImpl implements SitemapService { private static final Logger LOG = LoggerFactory.getLogger(SitemapServiceImpl.class); @Reference(cardinality = ReferenceCardinality.OPTIONAL, policyOption = ReferencePolicyOption.GREEDY) - private SitemapLinkExternalizer externalizer = SitemapLinkExternalizer.DEFAULT; + private SitemapLinkExternalizer externalizer; @Reference private JobManager jobManager; @Reference @@ -99,7 +100,7 @@ public class SitemapServiceImpl implements SitemapService { return getSitemapUrlsForNestedSitemapRoot(sitemapRoot); } - String url = externalizer.externalize(sitemapRoot); + String url = externalize(sitemapRoot); Collection<String> names = generatorManager.getGenerators(sitemapRoot).keySet(); if (url == null) { @@ -121,7 +122,7 @@ public class SitemapServiceImpl implements SitemapService { } else { location += storageInfo.getSitemapSelector() + '.' + SitemapServlet.SITEMAP_EXTENSION; } - infos.add(newSitemapInfo(location, storageInfo.getSize(), storageInfo.getEntries())); + infos.add(newSitemapInfo(storageInfo.getPath(), location, storageInfo.getSize(), storageInfo.getEntries())); } return infos; @@ -172,7 +173,7 @@ public class SitemapServiceImpl implements SitemapService { private Collection<SitemapInfo> getSitemapUrlsForNestedSitemapRoot(Resource sitemapRoot) { Collection<String> names = generatorManager.getGenerators(sitemapRoot).keySet(); Resource topLevelSitemapRoot = getTopLevelSitemapRoot(sitemapRoot); - String topLevelSitemapRootUrl = externalizer.externalize(topLevelSitemapRoot); + String topLevelSitemapRootUrl = externalize(topLevelSitemapRoot); if (topLevelSitemapRootUrl == null || names.isEmpty()) { LOG.debug("Could not create absolute urls for nested sitemaps at: {}", sitemapRoot.getPath()); @@ -187,7 +188,7 @@ public class SitemapServiceImpl implements SitemapService { String selector = getSitemapSelector(sitemapRoot, topLevelSitemapRoot, name); String location = topLevelSitemapRootUrl + selector + '.' + SitemapServlet.SITEMAP_EXTENSION; if (onDemandNames.contains(name)) { - infos.add(newSitemapInfo(location, -1, -1)); + infos.add(newSitemapInfo(null, location, -1, -1)); } else { if (storageInfos == null) { storageInfos = storage.getSitemaps(sitemapRoot, names); @@ -197,7 +198,7 @@ public class SitemapServiceImpl implements SitemapService { .findFirst(); if (storageInfoOpt.isPresent()) { SitemapStorageInfo storageInfo = storageInfoOpt.get(); - infos.add(newSitemapInfo(location, storageInfo.getSize(), storageInfo.getEntries())); + infos.add(newSitemapInfo(storageInfo.getPath(), location, storageInfo.getSize(), storageInfo.getEntries())); } } } @@ -205,25 +206,29 @@ public class SitemapServiceImpl implements SitemapService { return infos; } - + private String externalize(Resource resource) { + return (externalizer == null ? SitemapLinkExternalizer.DEFAULT : externalizer).externalize(resource); + } private SitemapInfo newSitemapIndexInfo(@NotNull String url) { - return new SitemapInfoImpl(url, -1, -1, true, true); + return new SitemapInfoImpl(null, url, -1, -1, true, true); } - private SitemapInfo newSitemapInfo(@NotNull String url, int size, int entries) { - return new SitemapInfoImpl(url, size, entries, false, isWithinLimits(size, entries)); + private SitemapInfo newSitemapInfo(@Nullable String path, @NotNull String url, int size, int entries) { + return new SitemapInfoImpl(path, url, size, entries, false, isWithinLimits(size, entries)); } private static class SitemapInfoImpl implements SitemapInfo { private final String url; + private final String path; private final int size; private final int entries; private final boolean isIndex; private final boolean withinLimits; - private SitemapInfoImpl(@NotNull String url, int size, int entries, boolean isIndex, boolean withinLimits) { + private SitemapInfoImpl(@Nullable String path, @NotNull String url, int size, int entries, boolean isIndex, boolean withinLimits) { + this.path = path; this.url = url; this.size = size; this.entries = entries; @@ -231,6 +236,12 @@ public class SitemapServiceImpl implements SitemapService { this.withinLimits = withinLimits; } + @Nullable + @Override + public String getStoragePath() { + return path; + } + @NotNull @Override public String getUrl() { diff --git a/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapServlet.java b/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapServlet.java index a254a10..a7828b1 100644 --- a/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapServlet.java +++ b/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapServlet.java @@ -79,7 +79,7 @@ public class SitemapServlet extends SlingSafeMethodsServlet { }; @Reference(cardinality = ReferenceCardinality.OPTIONAL, policyOption = ReferencePolicyOption.GREEDY) - private SitemapLinkExternalizer externalizer = SitemapLinkExternalizer.DEFAULT; + private SitemapLinkExternalizer externalizer; @Reference private SitemapGeneratorManager generatorManager; @Reference @@ -134,8 +134,8 @@ public class SitemapServlet extends SlingSafeMethodsServlet { // add any sitemap from the storage for (SitemapStorageInfo storageInfo : storage.getSitemaps(topLevelSitemapRoot)) { if (!addedSitemapSelectors.contains(storageInfo.getSitemapSelector())) { - String location = externalizer.externalize(request, getSitemapLink(topLevelSitemapRoot, - storageInfo.getSitemapSelector())); + String location = externalize(request, + getSitemapLink(topLevelSitemapRoot, storageInfo.getSitemapSelector())); Calendar lastModified = storageInfo.getLastModified(); if (location != null && lastModified != null) { sitemapIndex.addSitemap(location, lastModified.toInstant()); @@ -209,7 +209,7 @@ public class SitemapServlet extends SlingSafeMethodsServlet { // applicable names we may serve directly, not applicable names, if any, we have to serve from storage for (String applicableName : applicableNames) { String sitemapSelector = getSitemapSelector(sitemapRoot, sitemapRoot, applicableName); - String location = externalizer.externalize(request, getSitemapLink(sitemapRoot, sitemapSelector)); + String location = externalize(request, getSitemapLink(sitemapRoot, sitemapSelector)); if (location != null) { index.addSitemap(location); addedSitemapSelectors.add(sitemapSelector); @@ -222,6 +222,10 @@ public class SitemapServlet extends SlingSafeMethodsServlet { return addedSitemapSelectors; } + private String externalize(SlingHttpServletRequest request, String uri) { + return (externalizer == null ? SitemapLinkExternalizer.DEFAULT : externalizer).externalize(request, uri); + } + private static String getSitemapLink(Resource sitemapRoot, String sitemapSelector) { String link = sitemapRoot.getPath() + '.' + SITEMAP_SELECTOR + '.'; if (SITEMAP_SELECTOR.equals(sitemapSelector)) { diff --git a/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapStorage.java b/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapStorage.java index 4ffa9c4..e6f41ea 100644 --- a/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapStorage.java +++ b/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapStorage.java @@ -51,7 +51,7 @@ import static org.apache.sling.sitemap.impl.SitemapUtil.*; service = {SitemapStorage.class, Runnable.class}, property = { Scheduler.PROPERTY_SCHEDULER_NAME + "=sitemap-storage-cleanup", - Scheduler.PROPERTY_SCHEDULER_CONCURRENT + "=false", + Scheduler.PROPERTY_SCHEDULER_CONCURRENT + ":Boolean=false", Scheduler.PROPERTY_SCHEDULER_RUN_ON + "=" + Scheduler.VALUE_RUN_ON_SINGLE } ) @@ -125,7 +125,7 @@ public class SitemapStorage implements Runnable { } @NotNull - ValueMap getState(@NotNull Resource sitemapRoot, @NotNull String name) throws IOException { + public ValueMap getState(@NotNull Resource sitemapRoot, @NotNull String name) throws IOException { String statePath = getSitemapFilePath(sitemapRoot, name) + STATE_EXTENSION; try (ResourceResolver resolver = resourceResolverFactory.getServiceResourceResolver(AUTH)) { Resource state = resolver.getResource(statePath); @@ -142,7 +142,7 @@ public class SitemapStorage implements Runnable { } } - void writeState(@NotNull Resource sitemapRoot, @NotNull String name, @NotNull Map<String, Object> state) + public void writeState(@NotNull Resource sitemapRoot, @NotNull String name, @NotNull Map<String, Object> state) throws IOException { String statePath = getSitemapFilePath(sitemapRoot, name) + STATE_EXTENSION; try (ResourceResolver resolver = resourceResolverFactory.getServiceResourceResolver(AUTH)) { @@ -170,7 +170,7 @@ public class SitemapStorage implements Runnable { } } - void removeState(@NotNull Resource sitemapRoot, @NotNull String name) throws IOException { + public void removeState(@NotNull Resource sitemapRoot, @NotNull String name) throws IOException { String statePath = getSitemapFilePath(sitemapRoot, name) + STATE_EXTENSION; try (ResourceResolver resolver = resourceResolverFactory.getServiceResourceResolver(AUTH)) { Resource stateResource = resolver.getResource(statePath); @@ -183,7 +183,7 @@ public class SitemapStorage implements Runnable { } } - String writeSitemap(@NotNull Resource sitemapRoot, @NotNull String name, @NotNull InputStream data, int size, + public String writeSitemap(@NotNull Resource sitemapRoot, @NotNull String name, @NotNull InputStream data, int size, int entries) throws IOException { String sitemapFilePath = getSitemapFilePath(sitemapRoot, name); String statePath = sitemapFilePath + STATE_EXTENSION; @@ -227,7 +227,7 @@ public class SitemapStorage implements Runnable { return sitemapFilePath; } - Set<SitemapStorageInfo> getSitemaps(Resource sitemapRoot) { + public Set<SitemapStorageInfo> getSitemaps(Resource sitemapRoot) { return getSitemaps(sitemapRoot, Collections.emptySet()); } @@ -242,7 +242,7 @@ public class SitemapStorage implements Runnable { * @param names * @return */ - Set<SitemapStorageInfo> getSitemaps(Resource sitemapRoot, Collection<String> names) { + public Set<SitemapStorageInfo> getSitemaps(Resource sitemapRoot, Collection<String> names) { Resource topLevelSitemapRoot = getTopLevelSitemapRoot(sitemapRoot); Predicate<SitemapStorageInfo> filter; @@ -279,7 +279,7 @@ public class SitemapStorage implements Runnable { } } - boolean copySitemap(Resource sitemapRoot, String sitemapSelector, OutputStream output) throws IOException { + public boolean copySitemap(Resource sitemapRoot, String sitemapSelector, OutputStream output) throws IOException { if (!isTopLevelSitemapRoot(sitemapRoot)) { return false; } diff --git a/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapUtil.java b/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapUtil.java index e5607bc..7ee438e 100644 --- a/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapUtil.java +++ b/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapUtil.java @@ -19,10 +19,11 @@ package org.apache.sling.sitemap.impl; import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.util.ISO9075; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; -import org.apache.sling.sitemap.generator.SitemapGenerator; import org.apache.sling.sitemap.SitemapService; +import org.apache.sling.sitemap.generator.SitemapGenerator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -31,6 +32,8 @@ import java.util.*; public class SitemapUtil { + private static final String JCR_SYSTEM_PATH = "/" + JcrConstants.JCR_SYSTEM + "/"; + private SitemapUtil() { super(); } @@ -151,12 +154,16 @@ public class SitemapUtil { */ @NotNull public static Iterator<Resource> findSitemapRoots(ResourceResolver resolver, String searchPath) { - return new Iterator<Resource>() { + String correctedSearchPath = searchPath == null ? "/" : searchPath; + StringBuilder query = new StringBuilder(correctedSearchPath.length() + 35); + query.append("/jcr:root").append(ISO9075.encodePath(correctedSearchPath)); + if (!correctedSearchPath.endsWith("/")) { + query.append('/'); + } + query.append("/*[@").append(SitemapService.PROPERTY_SITEMAP_ROOT).append('=').append(Boolean.TRUE).append(']'); - private final Iterator<Resource> hits = resolver.findResources( - "/jcr:root" + searchPath + "//*[@" + SitemapService.PROPERTY_SITEMAP_ROOT + "=true]", - Query.XPATH - ); + return new Iterator<Resource>() { + private final Iterator<Resource> hits = resolver.findResources(query.toString(), Query.XPATH); private Resource next = seek(); private Resource seek() { @@ -165,7 +172,9 @@ public class SitemapUtil { // skip a hit on the given searchPath itself. This may be when a search is done for descendant // sitemaps given the normalized sitemap root path and the sitemap root's jcr:content is in the // result set. - if (nextHit == null || nextHit.getPath().equals(searchPath)) { + if (nextHit == null + || nextHit.getPath().equals(correctedSearchPath) + || nextHit.getPath().startsWith(JCR_SYSTEM_PATH)) { continue; } return nextHit; diff --git a/sitemap/src/main/java/org/apache/sling/sitemap/impl/console/SitemapInventoryPlugin.java b/sitemap/src/main/java/org/apache/sling/sitemap/impl/console/SitemapInventoryPlugin.java new file mode 100644 index 0000000..8feb7bd --- /dev/null +++ b/sitemap/src/main/java/org/apache/sling/sitemap/impl/console/SitemapInventoryPlugin.java @@ -0,0 +1,205 @@ +/* + * 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.sitemap.impl.console; + +import org.apache.felix.inventory.Format; +import org.apache.felix.inventory.InventoryPrinter; +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceResolverFactory; +import org.apache.sling.commons.scheduler.Scheduler; +import org.apache.sling.sitemap.SitemapInfo; +import org.apache.sling.sitemap.SitemapService; +import org.apache.sling.sitemap.impl.SitemapUtil; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; + +@Component( + service = InventoryPrinter.class, + property = { + InventoryPrinter.NAME + "=slingsitemap", + InventoryPrinter.TITLE + "=Sling Sitemap", + InventoryPrinter.FORMAT + "=JSON", + InventoryPrinter.FORMAT + "=TEXT", + InventoryPrinter.WEBCONSOLE + "=true" + + } +) +public class SitemapInventoryPlugin implements InventoryPrinter { + + private static final Map<String, Object> AUTH = Collections.singletonMap( + ResourceResolverFactory.SUBSERVICE, "sitemap-reader"); + private static final Logger LOG = LoggerFactory.getLogger(SitemapInventoryPlugin.class); + + @Reference + private SitemapService sitemapService; + @Reference + private ResourceResolverFactory resourceResolverFactory; + + private BundleContext bundleContext; + + @Activate + protected void activate(BundleContext bundleContext) { + this.bundleContext = bundleContext; + } + + @Override + public void print(PrintWriter printWriter, Format format, boolean isZip) { + if (Format.JSON.equals(format)) { + printJson(printWriter); + } else if (Format.TEXT.equals(format)) { + printText(printWriter); + } + } + + private void printJson(PrintWriter pw) { + pw.print('{'); + pw.print("\"schedulers\":["); + boolean hasScheduler = false; + for (ServiceReference<?> ref : bundleContext.getBundle().getRegisteredServices()) { + Object schedulerExp = ref.getProperty(Scheduler.PROPERTY_SCHEDULER_EXPRESSION); + Object schedulerName = ref.getProperty(Scheduler.PROPERTY_SCHEDULER_NAME); + if (schedulerExp instanceof String && schedulerName instanceof String) { + if (hasScheduler) { + pw.print(','); + } + hasScheduler = true; + pw.print("{\"name\":\""); + pw.print(escapeDoubleQuotes((String) schedulerName)); + pw.print("\",\"expression\":\""); + pw.print(escapeDoubleQuotes((String) schedulerExp)); + pw.print("\"}"); + } + } + pw.print("],"); + + pw.print("\"roots\":{"); + try (ResourceResolver resolver = resourceResolverFactory.getServiceResourceResolver(AUTH)) { + Iterator<Resource> roots = SitemapUtil.findSitemapRoots(resolver, "/"); + while (roots.hasNext()) { + Resource root = roots.next(); + pw.print('"'); + pw.print(escapeDoubleQuotes(root.getPath())); + pw.print("\":["); + Iterator<SitemapInfo> infoIt = sitemapService.getSitemapInfo(root).iterator(); + while (infoIt.hasNext()) { + SitemapInfo info = infoIt.next(); + pw.print('{'); + pw.print("\"url\":\""); + pw.print(escapeDoubleQuotes(info.getUrl())); + pw.print('"'); + if (info.getStoragePath() != null) { + pw.print(",\"path\":\""); + pw.print(escapeDoubleQuotes(info.getStoragePath())); + pw.print("\",\"size\":"); + pw.print(info.getSize()); + pw.print(",\"entries\":"); + pw.print(info.getEntries()); + pw.print(",\"inLimits\":"); + pw.print(info.isWithinLimits()); + } + pw.print('}'); + if (infoIt.hasNext()) { + pw.print(','); + } + } + pw.print(']'); + if (roots.hasNext()) { + pw.print(','); + } + } + } catch (LoginException ex) { + pw.println("Failed to list sitemaps: " + ex.getMessage()); + LOG.warn("Failed to get inventory of sitemaps: {}", ex.getMessage(), ex); + } + pw.print('}'); + pw.print('}'); + } + + private void printText(PrintWriter pw) { + pw.println("Apache Sling Sitemap Schedulers"); + pw.println("-------------------------------"); + + for (ServiceReference<?> ref : bundleContext.getBundle().getRegisteredServices()) { + Object schedulerExp = ref.getProperty(Scheduler.PROPERTY_SCHEDULER_EXPRESSION); + Object schedulerName = ref.getProperty(Scheduler.PROPERTY_SCHEDULER_NAME); + if (schedulerExp != null && schedulerName != null) { + pw.print(" - Name: "); + pw.print(schedulerName); + pw.println(); + pw.print(" Expression: "); + pw.print(schedulerExp); + pw.println(); + } + } + + pw.println(); + pw.println(); + pw.println("Apache Sling Sitemap Roots"); + pw.println("--------------------------"); + + try (ResourceResolver resolver = resourceResolverFactory.getServiceResourceResolver(AUTH)) { + Iterator<Resource> roots = SitemapUtil.findSitemapRoots(resolver, "/"); + while (roots.hasNext()) { + Resource root = roots.next(); + pw.print(root.getPath()); + pw.print(':'); + pw.println(); + for (SitemapInfo info : sitemapService.getSitemapInfo(root)) { + pw.print(" - Url: "); + pw.print(info.getUrl()); + pw.println(); + if (info.getStoragePath() != null) { + pw.print(" Path: "); + pw.print(info.getStoragePath()); + pw.println(); + pw.print(" Bytes: "); + pw.print(info.getSize()); + pw.println(); + pw.print(" Urls: "); + pw.print(info.getEntries()); + pw.println(); + pw.print(" Within Limits: "); + pw.print(info.isWithinLimits() ? "yes": "no"); + pw.println(); + } + } + } + } catch (LoginException ex) { + pw.println("Failed to list sitemaps: " + ex.getMessage()); + LOG.warn("Failed to get inventory of sitemaps: {}", ex.getMessage(), ex); + } + } + + private static String escapeDoubleQuotes(String text) { + return text.replace("\"", "\\\""); + } +}
