SLING-9841 - Added support for filtering installs based on the target path and renamed the ContentLoaderService since only handles bundles and isn't a service
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 2fe432b..a4d1584 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
@@ -31,7 +31,7 @@
import org.apache.felix.hc.api.Result;
import org.apache.sling.jcr.api.SlingRepository;
import org.apache.sling.jcr.contentloader.internal.BundleHelper;
-import org.apache.sling.jcr.contentloader.internal.ContentLoaderService;
+import org.apache.sling.jcr.contentloader.internal.BundleContentLoaderListener;
import org.apache.sling.jcr.contentloader.internal.PathEntry;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
@@ -117,7 +117,7 @@
try {
metadataSession = repository.loginService(null, null);
- BundleHelper bundleHelper = new ContentLoaderService();
+ BundleHelper bundleHelper = new BundleContentLoaderListener();
for (Bundle bundle : bundles) {
String bundleSymbolicName = bundle.getSymbolicName();
@@ -155,9 +155,9 @@
}
} else {
try {
- final boolean contentAlreadyLoaded = ((Boolean) bundleContentInfo.get(ContentLoaderService.PROPERTY_CONTENT_LOADED)).booleanValue();
+ final boolean contentAlreadyLoaded = ((Boolean) bundleContentInfo.get(BundleContentLoaderListener.PROPERTY_CONTENT_LOADED)).booleanValue();
boolean isBundleUpdated = false;
- Calendar lastLoadedAt = (Calendar) bundleContentInfo.get(ContentLoaderService.PROPERTY_CONTENT_LOADED_AT);
+ Calendar lastLoadedAt = (Calendar) bundleContentInfo.get(BundleContentLoaderListener.PROPERTY_CONTENT_LOADED_AT);
if (lastLoadedAt != null && lastLoadedAt.getTimeInMillis() < bundle.getLastModified()) {
isBundleUpdated = true;
}
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoader.java
index 84dbeb6..7740765 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoader.java
@@ -25,6 +25,7 @@
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Enumeration;
@@ -33,7 +34,12 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
import java.util.StringTokenizer;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import javax.jcr.Item;
import javax.jcr.NoSuchWorkspaceException;
@@ -62,10 +68,31 @@
// bundles whose registration failed and should be retried
private List<Bundle> delayedBundles;
- public BundleContentLoader(BundleHelper bundleHelper, ContentReaderWhiteboard contentReaderWhiteboard) {
+ private final Predicate<String> pathFilter;
+
+ public BundleContentLoader(BundleHelper bundleHelper, ContentReaderWhiteboard contentReaderWhiteboard,
+ BundleContentLoaderConfiguration configuration) {
super(contentReaderWhiteboard);
this.bundleHelper = bundleHelper;
this.delayedBundles = new LinkedList<>();
+
+ List<Pattern> includes = Arrays
+ .stream(Optional.ofNullable(configuration).map(BundleContentLoaderConfiguration::includedTargets)
+ .orElse(new String[0]))
+ .filter(Objects::nonNull).map(Pattern::compile).collect(Collectors.toList());
+ List<Pattern> excludes = Arrays
+ .stream(Optional.ofNullable(configuration).map(BundleContentLoaderConfiguration::excludedTargets)
+ .orElse(new String[0]))
+ .filter(Objects::nonNull).map(Pattern::compile).collect(Collectors.toList());
+ this.pathFilter = path -> {
+ if (configuration == null || path == null) {
+ return true;
+ } else {
+ return includes.stream().anyMatch(p -> p.matcher(path).matches())
+ && excludes.stream().noneMatch(p -> p.matcher(path).matches());
+ }
+ };
+ log.debug("Using includes: {} and excludes: {}", includes, excludes);
}
public void dispose() {
@@ -95,8 +122,9 @@
if (registerBundleInternal(metadataSession, bundle, false, isUpdate)) {
// handle delayed bundles, might help now
int currentSize = -1;
- for (int i = delayedBundles.size(); i > 0 && currentSize != delayedBundles.size() && !delayedBundles.isEmpty(); i--) {
- for (Iterator<Bundle> di = delayedBundles.iterator(); di.hasNext(); ) {
+ for (int i = delayedBundles.size(); i > 0 && currentSize != delayedBundles.size()
+ && !delayedBundles.isEmpty(); i--) {
+ for (Iterator<Bundle> di = delayedBundles.iterator(); di.hasNext();) {
Bundle delayed = di.next();
if (registerBundleInternal(metadataSession, delayed, true, false)) {
di.remove();
@@ -110,7 +138,8 @@
}
}
- private boolean registerBundleInternal(final Session metadataSession, final Bundle bundle, final boolean isRetry, final boolean isUpdate) {
+ private boolean registerBundleInternal(final Session metadataSession, final Bundle bundle, final boolean isRetry,
+ final boolean isUpdate) {
// check if bundle has initial content
final Iterator<PathEntry> pathIter = PathEntry.getContentPaths(bundle);
@@ -120,10 +149,11 @@
}
try {
- bundleHelper.createRepositoryPath(metadataSession, ContentLoaderService.BUNDLE_CONTENT_NODE);
+ bundleHelper.createRepositoryPath(metadataSession, BundleContentLoaderListener.BUNDLE_CONTENT_NODE);
// check if the content has already been loaded
- final Map<String, Object> bundleContentInfo = bundleHelper.getBundleContentInfo(metadataSession, bundle, true);
+ final Map<String, Object> bundleContentInfo = bundleHelper.getBundleContentInfo(metadataSession, bundle,
+ true);
// if we don't get an info, someone else is currently loading
if (bundleContentInfo == null) {
@@ -133,16 +163,19 @@
boolean success = false;
List<String> createdNodes = null;
try {
- final boolean contentAlreadyLoaded = ((Boolean) bundleContentInfo.get(ContentLoaderService.PROPERTY_CONTENT_LOADED)).booleanValue();
+ final boolean contentAlreadyLoaded = ((Boolean) bundleContentInfo
+ .get(BundleContentLoaderListener.PROPERTY_CONTENT_LOADED)).booleanValue();
boolean isBundleUpdated = false;
- Calendar lastLoadedAt = (Calendar) bundleContentInfo.get(ContentLoaderService.PROPERTY_CONTENT_LOADED_AT);
+ Calendar lastLoadedAt = (Calendar) bundleContentInfo
+ .get(BundleContentLoaderListener.PROPERTY_CONTENT_LOADED_AT);
if (lastLoadedAt != null && lastLoadedAt.getTimeInMillis() < bundle.getLastModified()) {
isBundleUpdated = true;
}
if (!isUpdate && !isBundleUpdated && contentAlreadyLoaded) {
log.info("Content of bundle already loaded {}.", bundle.getSymbolicName());
} else {
- createdNodes = installContent(metadataSession, bundle, pathIter, contentAlreadyLoaded && !isBundleUpdated);
+ createdNodes = installContent(metadataSession, bundle, pathIter,
+ contentAlreadyLoaded && !isBundleUpdated);
if (isRetry) {
// log success of retry
log.info("Retrying to load initial content for bundle {} succeeded.", bundle.getSymbolicName());
@@ -159,7 +192,9 @@
// if we are retrying we already logged this message once, so we
// won't log it again
if (!isRetry) {
- log.error("Cannot load initial content for bundle " + bundle.getSymbolicName() + " : " + re.getMessage(), re);
+ log.error(
+ "Cannot load initial content for bundle " + bundle.getSymbolicName() + " : " + re.getMessage(),
+ re);
}
}
return false;
@@ -176,7 +211,7 @@
delayedBundles.remove(bundle);
} else {
try {
- bundleHelper.createRepositoryPath(session, ContentLoaderService.BUNDLE_CONTENT_NODE);
+ bundleHelper.createRepositoryPath(session, BundleContentLoaderListener.BUNDLE_CONTENT_NODE);
final Map<String, Object> bundleContentInfo = bundleHelper.getBundleContentInfo(session, bundle, false);
@@ -187,13 +222,15 @@
}
try {
- uninstallContent(session, bundle, (String[]) bundleContentInfo.get(ContentLoaderService.PROPERTY_UNINSTALL_PATHS));
+ uninstallContent(session, bundle,
+ (String[]) bundleContentInfo.get(BundleContentLoaderListener.PROPERTY_UNINSTALL_PATHS));
bundleHelper.contentIsUninstalled(session, bundle);
} finally {
bundleHelper.unlockBundleContentInfo(session, bundle, false, null);
}
} catch (RepositoryException re) {
- log.error("Cannot remove initial content for bundle " + bundle.getSymbolicName() + " : " + re.getMessage(), re);
+ log.error("Cannot remove initial content for bundle " + bundle.getSymbolicName() + " : "
+ + re.getMessage(), re);
}
}
}
@@ -205,7 +242,8 @@
*
* @return If the content should be removed on uninstall, a list of top nodes
*/
- private List<String> installContent(final Session defaultSession, final Bundle bundle, final Iterator<PathEntry> pathIter, final boolean contentAlreadyLoaded) throws RepositoryException {
+ private List<String> installContent(final Session defaultSession, final Bundle bundle,
+ final Iterator<PathEntry> pathIter, final boolean contentAlreadyLoaded) throws RepositoryException {
final List<String> createdNodes = new ArrayList<>();
final Map<String, Session> createdSessions = new HashMap<>();
@@ -215,6 +253,12 @@
try {
while (pathIter.hasNext()) {
final PathEntry pathEntry = pathIter.next();
+
+ if (!pathFilter.test(pathEntry.getTarget())) {
+ log.debug("Path {} excluded by configuration", pathEntry.getPath());
+ continue;
+ }
+
if (!contentAlreadyLoaded || pathEntry.isOverwrite()) {
String workspace = pathEntry.getWorkspace();
final Session targetSession;
@@ -232,7 +276,8 @@
final Node targetNode = getTargetNode(targetSession, pathEntry.getTarget());
if (targetNode != null) {
- installFromPath(bundle, pathEntry.getPath(), pathEntry, targetNode, pathEntry.isUninstall() ? createdNodes : null, contentCreator);
+ installFromPath(bundle, pathEntry.getPath(), pathEntry, targetNode,
+ pathEntry.isUninstall() ? createdNodes : null, contentCreator);
}
}
}
@@ -263,8 +308,8 @@
// finally check in versionable nodes
for (final Node versionable : contentCreator.getVersionables()) {
- VersionManager versionManager = versionable.getSession().getWorkspace().getVersionManager();
- versionManager.checkin(versionable.getPath());
+ VersionManager versionManager = versionable.getSession().getWorkspace().getVersionManager();
+ versionManager.checkin(versionable.getPath());
}
} finally {
try {
@@ -296,12 +341,15 @@
* @param path The path
* @param configuration
* @param parent The parent node.
- * @param createdNodes An optional list to store all new nodes. This list is used for an uninstall
+ * @param createdNodes An optional list to store all new nodes. This list is
+ * used for an uninstall
* @throws RepositoryException
*/
- private void installFromPath(final Bundle bundle, final String path, final PathEntry configuration, final Node parent, final List<String> createdNodes, final DefaultContentCreator contentCreator) throws RepositoryException {
+ private void installFromPath(final Bundle bundle, final String path, final PathEntry configuration,
+ final Node parent, final List<String> createdNodes, final DefaultContentCreator contentCreator)
+ throws RepositoryException {
- // init content creator
+ // init content creator
contentCreator.init(configuration, getContentReaders(), createdNodes, null);
final Map<String, Node> processedEntries = new HashMap<>();
@@ -314,7 +362,8 @@
log.info("install: No initial content entries at {} in bundle {}", path, bundle.getSymbolicName());
return;
}
- // we have a single file content, let's check if this has an content reader extension
+ // we have a single file content, let's check if this has an content reader
+ // extension
for (String ext : contentCreator.getContentReaders().keySet()) {
if (path.endsWith(ext)) {
@@ -384,7 +433,9 @@
* @param createdNodes
* @throws RepositoryException
*/
- private void handleFile(final String entry, final Bundle bundle, final Map<String, Node> processedEntries, final PathEntry configuration, final Node parent, final List<String> createdNodes, final DefaultContentCreator contentCreator) throws RepositoryException {
+ private void handleFile(final String entry, final Bundle bundle, final Map<String, Node> processedEntries,
+ final PathEntry configuration, final Node parent, final List<String> createdNodes,
+ final DefaultContentCreator contentCreator) throws RepositoryException {
final URL file = bundle.getEntry(entry);
final String name = getName(entry);
@@ -456,8 +507,8 @@
* @return
* @throws RepositoryException
*/
- private Node createNode(Node parent, String name, URL resourceUrl, final DefaultContentCreator contentCreator, PathEntry configuration)
- throws RepositoryException {
+ private Node createNode(Node parent, String name, URL resourceUrl, final DefaultContentCreator contentCreator,
+ PathEntry configuration) throws RepositoryException {
final String resourcePath = resourceUrl.getPath().toLowerCase();
InputStream contentStream = null;
@@ -523,7 +574,8 @@
* @throws IOException
* @throws RepositoryException
*/
- private void createFile(PathEntry configuration, Node parent, URL source, List<String> createdNodes, final DefaultContentCreator contentCreator) throws IOException, RepositoryException {
+ private void createFile(PathEntry configuration, Node parent, URL source, List<String> createdNodes,
+ final DefaultContentCreator contentCreator) throws IOException, RepositoryException {
final String srcPath = source.getPath();
int pos = srcPath.lastIndexOf('/');
@@ -547,12 +599,12 @@
}
/**
- * Gets and decodes the name part of the <code>path</code>. The name is
- * the part of the path after the last slash (or the complete path if no
- * slash is contained). To support names containing unsupported characters
- * such as colon (<code>:</code>), names may be URL encoded (see
- * <code>java.net.URLEncoder</code>) using the <i>UTF-8</i> character
- * encoding. In this case, this method decodes the name using the
+ * Gets and decodes the name part of the <code>path</code>. The name is the part
+ * of the path after the last slash (or the complete path if no slash is
+ * contained). To support names containing unsupported characters such as colon
+ * (<code>:</code>), names may be URL encoded (see
+ * <code>java.net.URLEncoder</code>) using the <i>UTF-8</i> character encoding.
+ * In this case, this method decodes the name using the
* <code>java.net.URLDecoder</code> class with the <i>UTF-8</i> character
* encoding.
*
@@ -618,6 +670,10 @@
log.debug("Uninstalling initial content from bundle {}", bundle.getSymbolicName());
if (uninstallPaths != null && uninstallPaths.length > 0) {
for (String path : uninstallPaths) {
+ if (!pathFilter.test(path)) {
+ log.debug("Path {} excluded by configuration", path);
+ continue;
+ }
final Session targetSession;
final int wsSepPos = path.indexOf(":/");
@@ -662,7 +718,8 @@
}
}
} catch (RepositoryException re) {
- log.warn("Failure to rollback uninstalling initial content for bundle {}", bundle.getSymbolicName(), re);
+ log.warn("Failure to rollback uninstalling initial content for bundle {}", bundle.getSymbolicName(),
+ re);
}
for (Session session : createdSessions.values()) {
@@ -682,7 +739,8 @@
/**
* Return the parent node descriptor (ROOT).
*/
- private Descriptor getParentNodeDescriptor(final Bundle bundle, final String path, final DefaultContentCreator contentCreator) {
+ private Descriptor getParentNodeDescriptor(final Bundle bundle, final String path,
+ final DefaultContentCreator contentCreator) {
for (Map.Entry<String, ContentReader> entry : contentCreator.getContentReaders().entrySet()) {
if (entry.getValue() != null) {
@@ -706,10 +764,11 @@
}
/**
- * Imports mixin nodes and properties (and optionally child nodes) of the
- * parent node.
+ * Imports mixin nodes and properties (and optionally child nodes) of the parent
+ * node.
*/
- private URL importParentNode(Bundle bundle, String path, Node parent, final DefaultContentCreator contentCreator) throws RepositoryException {
+ private URL importParentNode(Bundle bundle, String path, Node parent, final DefaultContentCreator contentCreator)
+ throws RepositoryException {
final Descriptor descriptor = getParentNodeDescriptor(bundle, path, contentCreator);
// no parent descriptor (ROOT) found
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoaderConfiguration.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoaderConfiguration.java
new file mode 100644
index 0000000..1302778
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoaderConfiguration.java
@@ -0,0 +1,32 @@
+/*
+ * 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 org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@ObjectClassDefinition(name = "%contentloader.config.name", description = "%contentloader.config.description", localization = "OSGI-INF/l10n/bundle")
+public @interface BundleContentLoaderConfiguration {
+
+ @AttributeDefinition(name = "%includedTargets.name", description = "%includedTargets.description")
+ String[] includedTargets() default { "^\\/.*$" };
+
+ @AttributeDefinition(name = "%excludedTargets.name", description = "%excludedTargets.description")
+ String[] excludedTargets() default {};
+}
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoaderListener.java
similarity index 76%
rename from src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
rename to src/main/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoaderListener.java
index d68d605..b671133 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoaderListener.java
@@ -43,25 +43,26 @@
import org.osgi.framework.SynchronousBundleListener;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * The <code>ContentLoaderService</code> is the service
- * providing the following functionality:
+ * The <code>BundleContentLoaderListener</code> is the service providing the
+ * following functionality:
* <ul>
* <li>Bundle listener to load and unload initial content.
* </ul>
*
*/
-@Component(service = {},
- property = {
- Constants.SERVICE_VENDOR + "=The Apache Software Foundation",
- Constants.SERVICE_DESCRIPTION + "=Apache Sling Content Loader Implementation"
- })
-public class ContentLoaderService implements SynchronousBundleListener, BundleHelper {
+@Component(service = {}, property = { Constants.SERVICE_VENDOR + "=The Apache Software Foundation",
+ Constants.SERVICE_DESCRIPTION
+ + "=Apache Sling Content Loader Implementation" }, 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";
@@ -82,8 +83,8 @@
private SlingRepository repository;
/**
- * The MimeTypeService used by the bundle content loader to
- * resolve MIME types for files to be installed.
+ * The MimeTypeService used by the bundle content loader to resolve MIME types
+ * for files to be installed.
*/
@Reference
private MimeTypeService mimeTypeService;
@@ -95,8 +96,8 @@
private ContentReaderWhiteboard contentReaderWhiteboard;
/**
- * The initial content loader which is called to load initial content up
- * into the repository when the providing bundle is installed.
+ * The initial content loader which is called to load initial content up into
+ * the repository when the providing bundle is installed.
*/
private BundleContentLoader bundleContentLoader;
@@ -117,12 +118,12 @@
// ---------- BundleListener -----------------------------------------------
/**
- * Loads and unloads any content provided by the bundle whose state
- * changed. If the bundle has been started, the content is loaded. If
- * the bundle is about to stop, the content are unloaded.
+ * Loads and unloads any content provided by the bundle whose state changed. If
+ * the bundle has been started, the content is loaded. If the bundle is about to
+ * stop, the content are unloaded.
*
* @param event The <code>BundleEvent</code> representing the bundle state
- * change.
+ * change.
*/
@Override
public synchronized void bundleChanged(BundleEvent event) {
@@ -149,10 +150,8 @@
isUpdate = this.updatedBundles.remove(bundle.getSymbolicName());
bundleContentLoader.registerBundle(session, bundle, isUpdate);
} catch (Exception t) {
- log.error(
- "bundleChanged: Problem loading initial content of bundle "
- + bundle.getSymbolicName() + " ("
- + bundle.getBundleId() + ")", t);
+ log.error("bundleChanged: Problem loading initial content of bundle " + bundle.getSymbolicName()
+ + " (" + bundle.getBundleId() + ")", t);
} finally {
this.ungetSession(session);
}
@@ -167,10 +166,8 @@
session = this.getSession();
bundleContentLoader.unregisterBundle(session, bundle);
} catch (Exception t) {
- log.error(
- "bundleChanged: Problem unloading initial content of bundle "
- + bundle.getSymbolicName() + " ("
- + bundle.getBundleId() + ")", t);
+ log.error("bundleChanged: Problem unloading initial content of bundle " + bundle.getSymbolicName()
+ + " (" + bundle.getBundleId() + ")", t);
} finally {
this.ungetSession(session);
}
@@ -192,16 +189,16 @@
@Override
public void createRepositoryPath(final Session writerSession, final String repositoryPath)
- throws RepositoryException {
- if ( !writerSession.itemExists(repositoryPath) ) {
+ throws RepositoryException {
+ if (!writerSession.itemExists(repositoryPath)) {
Node node = writerSession.getRootNode();
String path = repositoryPath.substring(1);
int pos = path.lastIndexOf('/');
- if ( pos != -1 ) {
+ if (pos != -1) {
final StringTokenizer st = new StringTokenizer(path.substring(0, pos), "/");
- while ( st.hasMoreTokens() ) {
+ while (st.hasMoreTokens()) {
final String token = st.nextToken();
- if ( !node.hasNode(token) ) {
+ if (!node.hasNode(token)) {
node.addNode(token, "sling:Folder");
writerSession.save();
}
@@ -209,7 +206,7 @@
}
path = path.substring(pos + 1);
}
- if ( !node.hasNode(path) ) {
+ if (!node.hasNode(path)) {
node.addNode(path, "sling:Folder");
writerSession.save();
}
@@ -220,9 +217,9 @@
/** Activates this component, called by SCR before registering as a service */
@Activate
- protected synchronized void activate(BundleContext bundleContext) {
+ protected synchronized void activate(BundleContext bundleContext, BundleContentLoaderConfiguration configuration) {
this.slingId = this.settingsService.getSlingId();
- this.bundleContentLoader = new BundleContentLoader(this, contentReaderWhiteboard);
+ this.bundleContentLoader = new BundleContentLoader(this, contentReaderWhiteboard, configuration);
bundleContext.addBundleListener(this);
@@ -230,8 +227,7 @@
try {
session = this.getSession();
this.createRepositoryPath(session, BUNDLE_CONTENT_NODE);
- log.debug(
- "Activated - attempting to load content from all "
+ log.debug("Activated - attempting to load content from all "
+ "bundles which are neither INSTALLED nor UNINSTALLED");
int ignored = 0;
@@ -247,29 +243,25 @@
}
- log.debug(
- "Out of {} bundles, {} were not in a suitable state for initial content loading",
- bundles.length, ignored
- );
+ log.debug("Out of {} bundles, {} were not in a suitable state for initial content loading", bundles.length,
+ ignored);
} catch (Exception t) {
log.error("activate: Problem while loading initial content and"
- + " registering mappings for existing bundles", t);
+ + " registering mappings for existing bundles", t);
} finally {
this.ungetSession(session);
}
}
-
+
private void loadBundle(Bundle bundle, Session session) throws RepositoryException {
try {
bundleContentLoader.registerBundle(session, bundle, false);
} catch (Exception t) {
- log.error(
- "Problem loading initial content of bundle "
- + bundle.getSymbolicName() + " ("
+ log.error("Problem loading initial content of bundle " + bundle.getSymbolicName() + " ("
+ bundle.getBundleId() + ")", t);
} finally {
- if ( session.hasPendingChanges() ) {
+ if (session.hasPendingChanges()) {
session.refresh(false);
}
}
@@ -280,7 +272,7 @@
protected synchronized void deactivate(BundleContext bundleContext) {
bundleContext.removeBundleListener(this);
- if ( this.bundleContentLoader != null ) {
+ if (this.bundleContentLoader != null) {
this.bundleContentLoader.dispose();
this.bundleContentLoader = null;
}
@@ -297,8 +289,7 @@
* Returns an administrative session to the default workspace.
*/
@Override
- public Session getSession()
- throws RepositoryException {
+ public Session getSession() throws RepositoryException {
return getRepository().loginService(null, null);
}
@@ -314,7 +305,7 @@
* Return the administrative session and close it.
*/
private void ungetSession(final Session session) {
- if ( session != null ) {
+ if (session != null) {
try {
session.logout();
} catch (Exception t) {
@@ -325,6 +316,7 @@
/**
* Return the bundle content info and make an exclusive lock.
+ *
* @param session
* @param bundle
* @return The map of bundle content info or null.
@@ -332,11 +324,11 @@
*/
@Override
public Map<String, Object> getBundleContentInfo(final Session session, final Bundle bundle, boolean create)
- throws RepositoryException {
+ throws RepositoryException {
final String nodeName = bundle.getSymbolicName();
- final Node parentNode = (Node)session.getItem(BUNDLE_CONTENT_NODE);
- if ( !parentNode.hasNode(nodeName) ) {
- if ( !create ) {
+ final Node parentNode = (Node) session.getItem(BUNDLE_CONTENT_NODE);
+ if (!parentNode.hasNode(nodeName)) {
+ if (!create) {
return null;
}
try {
@@ -350,33 +342,31 @@
}
}
final Node bcNode = parentNode.getNode(nodeName);
- if ( bcNode.isLocked() ) {
+ if (bcNode.isLocked()) {
return null;
}
try {
- LockManager lockManager = session.getWorkspace().getLockManager();
- lockManager.lock(bcNode.getPath(),
- false, // isDeep
- true, // isSessionScoped
- Long.MAX_VALUE, // timeoutHint
- null); // ownerInfo
+ LockManager lockManager = session.getWorkspace().getLockManager();
+ lockManager.lock(bcNode.getPath(), false, // isDeep
+ true, // isSessionScoped
+ Long.MAX_VALUE, // timeoutHint
+ null); // ownerInfo
} catch (LockException le) {
return null;
}
final Map<String, Object> info = new HashMap<>();
- if ( bcNode.hasProperty(PROPERTY_CONTENT_LOADED_AT)) {
+ if (bcNode.hasProperty(PROPERTY_CONTENT_LOADED_AT)) {
info.put(PROPERTY_CONTENT_LOADED_AT, bcNode.getProperty(PROPERTY_CONTENT_LOADED_AT).getDate());
}
- if ( bcNode.hasProperty(PROPERTY_CONTENT_LOADED) ) {
- info.put(PROPERTY_CONTENT_LOADED,
- bcNode.getProperty(PROPERTY_CONTENT_LOADED).getBoolean());
+ if (bcNode.hasProperty(PROPERTY_CONTENT_LOADED)) {
+ info.put(PROPERTY_CONTENT_LOADED, bcNode.getProperty(PROPERTY_CONTENT_LOADED).getBoolean());
} else {
info.put(PROPERTY_CONTENT_LOADED, false);
}
- if ( bcNode.hasProperty(PROPERTY_UNINSTALL_PATHS) ) {
+ if (bcNode.hasProperty(PROPERTY_UNINSTALL_PATHS)) {
final Value[] values = bcNode.getProperty(PROPERTY_UNINSTALL_PATHS).getValues();
final String[] s = new String[values.length];
- for(int i=0; i<values.length; i++) {
+ for (int i = 0; i < values.length; i++) {
s[i] = values[i].getString();
}
info.put(PROPERTY_UNINSTALL_PATHS, s);
@@ -385,41 +375,37 @@
}
@Override
- public void unlockBundleContentInfo(final Session session,
- final Bundle bundle,
- final boolean contentLoaded,
- final List<String> createdNodes)
- throws RepositoryException {
+ public void unlockBundleContentInfo(final Session session, final Bundle bundle, final boolean contentLoaded,
+ final List<String> createdNodes) throws RepositoryException {
final String nodeName = bundle.getSymbolicName();
- final Node parentNode = (Node)session.getItem(BUNDLE_CONTENT_NODE);
+ final Node parentNode = (Node) session.getItem(BUNDLE_CONTENT_NODE);
final Node bcNode = parentNode.getNode(nodeName);
- if ( contentLoaded ) {
+ if (contentLoaded) {
bcNode.setProperty(PROPERTY_CONTENT_LOADED, contentLoaded);
bcNode.setProperty(PROPERTY_CONTENT_LOADED_AT, Calendar.getInstance());
bcNode.setProperty(PROPERTY_CONTENT_LOADED_BY, this.slingId);
- bcNode.setProperty(PROPERTY_CONTENT_UNLOADED_AT, (String)null);
- bcNode.setProperty(PROPERTY_CONTENT_UNLOADED_BY, (String)null);
- if ( createdNodes != null && !createdNodes.isEmpty() ) {
+ bcNode.setProperty(PROPERTY_CONTENT_UNLOADED_AT, (String) null);
+ bcNode.setProperty(PROPERTY_CONTENT_UNLOADED_BY, (String) null);
+ if (createdNodes != null && !createdNodes.isEmpty()) {
bcNode.setProperty(PROPERTY_UNINSTALL_PATHS, createdNodes.toArray(new String[createdNodes.size()]));
}
session.save();
}
LockManager lockManager = session.getWorkspace().getLockManager();
- lockManager.unlock(bcNode.getPath());
+ lockManager.unlock(bcNode.getPath());
}
@Override
- public void contentIsUninstalled(final Session session,
- final Bundle bundle) {
+ public void contentIsUninstalled(final Session session, final Bundle bundle) {
final String nodeName = bundle.getSymbolicName();
try {
- final Node parentNode = (Node)session.getItem(BUNDLE_CONTENT_NODE);
- if ( parentNode.hasNode(nodeName) ) {
+ final Node parentNode = (Node) session.getItem(BUNDLE_CONTENT_NODE);
+ if (parentNode.hasNode(nodeName)) {
final Node bcNode = parentNode.getNode(nodeName);
bcNode.setProperty(PROPERTY_CONTENT_LOADED, false);
bcNode.setProperty(PROPERTY_CONTENT_UNLOADED_AT, Calendar.getInstance());
bcNode.setProperty(PROPERTY_CONTENT_UNLOADED_BY, this.slingId);
- bcNode.setProperty(PROPERTY_UNINSTALL_PATHS, (String[])null);
+ bcNode.setProperty(PROPERTY_UNINSTALL_PATHS, (String[]) null);
session.save();
}
} catch (RepositoryException re) {
diff --git a/src/main/resources/OSGI-INF/l10n/bundle.properties b/src/main/resources/OSGI-INF/l10n/bundle.properties
new file mode 100644
index 0000000..ead6b55
--- /dev/null
+++ b/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -0,0 +1,33 @@
+#
+# 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.
+#
+
+# This file contains localization strings for configuration labels and
+# descriptions as used in the metatype.xml descriptor generated by the
+# the Sling SCR plugin
+
+contentloader.config.name=Apache Sling JCR ContentLoader - Bundle Content Loader
+contentloader.config.description=Configure the path handling of the JCR ContentLoader Bundle Content Loader
+
+includedTargets.name=Included Targets
+includedTargets.description=An array of regular expressions for the Path Entry targets to include when installing content \
+will be evaluated first
+
+excludedTargets.name=Excluded Targets
+excludedTargets.description=An array of regular expressions for the Path Entry targets to exclude when installing content \
+will be evaluated after include
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderServiceTest.java b/src/test/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoaderListenerTest.java
similarity index 82%
rename from src/test/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderServiceTest.java
rename to src/test/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoaderListenerTest.java
index 2db48d5..0bdef08 100644
--- a/src/test/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderServiceTest.java
+++ b/src/test/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoaderListenerTest.java
@@ -18,10 +18,10 @@
*/
package org.apache.sling.jcr.contentloader.internal;
-import static org.apache.sling.jcr.contentloader.internal.ContentLoaderService.BUNDLE_CONTENT_NODE;
-import static org.apache.sling.jcr.contentloader.internal.ContentLoaderService.PROPERTY_CONTENT_LOADED;
-import static org.apache.sling.jcr.contentloader.internal.ContentLoaderService.PROPERTY_CONTENT_LOADED_AT;
-import static org.apache.sling.jcr.contentloader.internal.ContentLoaderService.PROPERTY_UNINSTALL_PATHS;
+import static org.apache.sling.jcr.contentloader.internal.BundleContentLoaderListener.BUNDLE_CONTENT_NODE;
+import static org.apache.sling.jcr.contentloader.internal.BundleContentLoaderListener.PROPERTY_CONTENT_LOADED;
+import static org.apache.sling.jcr.contentloader.internal.BundleContentLoaderListener.PROPERTY_CONTENT_LOADED_AT;
+import static org.apache.sling.jcr.contentloader.internal.BundleContentLoaderListener.PROPERTY_UNINSTALL_PATHS;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -53,7 +53,7 @@
import junitx.util.PrivateAccessor;
-public class ContentLoaderServiceTest {
+public class BundleContentLoaderListenerTest {
@Rule
public final SlingContext context = new SlingContext(ResourceResolverType.JCR_OAK);
@@ -61,7 +61,7 @@
@Rule
public ExpectedException thrown = ExpectedException.none();
- private ContentLoaderService underTest;
+ private BundleContentLoaderListener underTest;
private BundleContentLoader contentLoader;
private Session session;
@@ -73,13 +73,13 @@
session = context.resourceResolver().adaptTo(Session.class);
// register the content loader service
- underTest = context.registerInjectActivateService(new ContentLoaderService());
+ underTest = context.registerInjectActivateService(new BundleContentLoaderListener());
contentLoader = (BundleContentLoader) PrivateAccessor.getField(underTest, "bundleContentLoader");
}
- //-------ContentLoaderService#bundleChanged(BundleEvent)-------//
+ //-------BundleContentLoaderListener#bundleChanged(BundleEvent)-------//
//I'm not very sure how to test this method, it looks like side effect of this method goes very deep
- //And more affects BundleContentLoader than ContentLoaderService
+ //And more affects BundleContentLoader than BundleContentLoaderListener
@Test
public void testBundleResolvedBundleChanged() throws NoSuchFieldException, RepositoryException {
@@ -103,12 +103,12 @@
underTest.bundleChanged(new BundleEvent(BundleEvent.UNINSTALLED, bundle));
}
- //-------ContentLoaderService#bundleChanged(BundleEvent)-------//
+ //-------BundleContentLoaderListener#bundleChanged(BundleEvent)-------//
@Test
public void getContentInfoFromLockedNode() throws RepositoryException {
final Bundle bundle = createNewBundle();
- final Node bcNode = (Node)session.getItem(ContentLoaderService.BUNDLE_CONTENT_NODE);
+ final Node bcNode = (Node)session.getItem(BundleContentLoaderListener.BUNDLE_CONTENT_NODE);
bcNode.addNode(bundle.getSymbolicName()).addMixin("mix:lockable");
session.save();
LockManager lockManager = session.getWorkspace().getLockManager();
@@ -124,7 +124,7 @@
@Test
public void getContentInfoFromNotLockableNode() throws RepositoryException {
final Bundle bundle = createNewBundle();
- final Node bcNode = (Node)session.getItem(ContentLoaderService.BUNDLE_CONTENT_NODE);
+ final Node bcNode = (Node)session.getItem(BundleContentLoaderListener.BUNDLE_CONTENT_NODE);
bcNode.addNode(bundle.getSymbolicName()); //Node without lockable mixin
session.save();
@@ -134,7 +134,7 @@
@Test
public void getContentInfo() throws RepositoryException {
final Bundle bundle = createNewBundle();
- final Node bcNode = (Node)session.getItem(ContentLoaderService.BUNDLE_CONTENT_NODE);
+ final Node bcNode = (Node)session.getItem(BundleContentLoaderListener.BUNDLE_CONTENT_NODE);
final Node bundleContent = bcNode.addNode(bundle.getSymbolicName());
bundleContent.addMixin("mix:lockable");
@@ -156,7 +156,7 @@
assertTrue(props.containsKey(PROPERTY_UNINSTALL_PATHS));
}
- //-------ContentLoaderService#contentIsUninstalled(Session, Bundle)-------//
+ //-------BundleContentLoaderListener#contentIsUninstalled(Session, Bundle)-------//
@Test
public void testContentIsUninstalled() throws RepositoryException {
@@ -171,14 +171,14 @@
assertFalse(bcNode.getProperty(PROPERTY_CONTENT_LOADED).getBoolean());
}
- //-------ContentLoaderService#getMimeType(String)-------//
+ //-------BundleContentLoaderListener#getMimeType(String)-------//
@Test
public void testMimeTypeService(){
assertEquals("audio/mpeg", underTest.getMimeType("test.mp3"));
}
- //-------ContentLoaderService#getMimeType(String)-------//
+ //-------BundleContentLoaderListener#getMimeType(String)-------//
@Test
public void getSessionForWorkspace() throws RepositoryException {
diff --git a/src/test/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoaderTest.java b/src/test/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoaderTest.java
index a985701..368bfc7 100644
--- a/src/test/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoaderTest.java
+++ b/src/test/java/org/apache/sling/jcr/contentloader/internal/BundleContentLoaderTest.java
@@ -21,8 +21,11 @@
import static java.util.Collections.singletonMap;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
+import java.lang.annotation.Annotation;
+
import javax.jcr.Session;
import org.apache.sling.api.resource.Resource;
@@ -43,7 +46,9 @@
@Rule
public final SlingContext context = new SlingContext(ResourceResolverType.JCR_OAK);
- private BundleContentLoader contentLoader;
+ private BundleContentLoaderListener bundleHelper;
+
+ private ContentReaderWhiteboard whiteboard;
@Before
public void prepareContentLoader() throws Exception {
@@ -56,17 +61,17 @@
context.registerInjectActivateService(new ContentReaderWhiteboard());
// register the content loader service
- BundleHelper bundleHelper = context.registerInjectActivateService(new ContentLoaderService());
+ bundleHelper = context.registerInjectActivateService(new BundleContentLoaderListener());
- ContentReaderWhiteboard whiteboard = context.getService(ContentReaderWhiteboard.class);
+ whiteboard = context.getService(ContentReaderWhiteboard.class);
- contentLoader = new BundleContentLoader(bundleHelper, whiteboard);
}
-
@Test
public void loadContentWithSpecificPath() throws Exception {
+ BundleContentLoader contentLoader = new BundleContentLoader(bundleHelper, whiteboard, null);
+
Bundle mockBundle = newBundleWithInitialContent("SLING-INF/libs/app;path:=/libs/app");
contentLoader.registerBundle(context.resourceResolver().adaptTo(Session.class), mockBundle, false);
@@ -78,8 +83,102 @@
}
@Test
+ public void loadContentWithExcludes() throws Exception {
+
+ BundleContentLoader contentLoader = new BundleContentLoader(bundleHelper, whiteboard,
+ new BundleContentLoaderConfiguration() {
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return null;
+ }
+
+ @Override
+ public String[] includedTargets() {
+ return new String[] { "^/.*$" };
+ }
+
+ @Override
+ public String[] excludedTargets() {
+ return new String[] { "^/libs.*$" };
+ }
+
+ });
+
+ Bundle mockBundle = newBundleWithInitialContent(
+ "SLING-INF/libs/app;path:=/libs/app,SLING-INF/content/app;path:=/content/app");
+
+ contentLoader.registerBundle(context.resourceResolver().adaptTo(Session.class), mockBundle, false);
+
+ assertThat("Excluded resource imported", context.resourceResolver().getResource("/libs/app"), nullValue());
+ }
+
+
+ @Test
+ public void loadContentWithNullValue() throws Exception {
+
+ BundleContentLoader contentLoader = new BundleContentLoader(bundleHelper, whiteboard,
+ new BundleContentLoaderConfiguration() {
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return null;
+ }
+
+ @Override
+ public String[] includedTargets() {
+ return new String[] { "^/.*$" };
+ }
+
+ @Override
+ public String[] excludedTargets() {
+ return null;
+ }
+
+ });
+
+ Bundle mockBundle = newBundleWithInitialContent(
+ "SLING-INF/libs/app;path:=/libs/app,SLING-INF/content/app;path:=/content/app");
+
+ contentLoader.registerBundle(context.resourceResolver().adaptTo(Session.class), mockBundle, false);
+
+ assertThat("Excluded resource imported", context.resourceResolver().getResource("/libs/app"), notNullValue());
+ }
+
+
+ @Test
+ public void loadContentWithIncludes() throws Exception {
+
+ BundleContentLoader contentLoader = new BundleContentLoader(bundleHelper, whiteboard,
+ new BundleContentLoaderConfiguration() {
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return null;
+ }
+
+ @Override
+ public String[] includedTargets() {
+ return new String[] { "^/.*$" };
+ }
+
+ @Override
+ public String[] excludedTargets() {
+ return new String[] { "^/app.*$" };
+ }
+
+ });
+
+ Bundle mockBundle = newBundleWithInitialContent(
+ "SLING-INF/libs/app;path:=/libs/app");
+
+ contentLoader.registerBundle(context.resourceResolver().adaptTo(Session.class), mockBundle, false);
+
+ assertThat("Included resource not imported", context.resourceResolver().getResource("/libs/app"), notNullValue());
+ }
+
+ @Test
public void loadContentWithRootPath() throws Exception {
+ BundleContentLoader contentLoader = new BundleContentLoader(bundleHelper, whiteboard, null);
+
Bundle mockBundle = newBundleWithInitialContent("SLING-INF/");
contentLoader.registerBundle(context.resourceResolver().adaptTo(Session.class), mockBundle, false);
@@ -94,9 +193,12 @@
@Ignore("TODO - unregister or somehow ignore the XmlReader component for this test")
public void loadXmlAsIs() throws Exception {
+ BundleContentLoader contentLoader = new BundleContentLoader(bundleHelper, whiteboard, null);
+
dumpRepo("/", 2);
- Bundle mockBundle = newBundleWithInitialContent("SLING-INF/libs/app;path:=/libs/app;ignoreImportProviders:=xml");
+ Bundle mockBundle = newBundleWithInitialContent(
+ "SLING-INF/libs/app;path:=/libs/app;ignoreImportProviders:=xml");
contentLoader.registerBundle(context.resourceResolver().adaptTo(Session.class), mockBundle, false);
@@ -120,27 +222,25 @@
return mockBundle;
}
-
private void dumpRepo(String startPath, int maxDepth) {
dumpRepo0(startPath, maxDepth, 0);
}
-
private void dumpRepo0(String startPath, int maxDepth, int currentDepth) {
Resource resource = context.resourceResolver().getResource(startPath);
StringBuilder format = new StringBuilder();
- for ( int i = 0 ;i < currentDepth ; i++) {
+ for (int i = 0; i < currentDepth; i++) {
format.append(" ");
}
format.append("%s [%s]%n");
- String name = resource.getName().length() == 0 ? "/" : resource.getName();
+ String name = resource.getName().length() == 0 ? "/" : resource.getName();
System.out.format(format.toString(), name, resource.getResourceType());
currentDepth++;
- if ( currentDepth > maxDepth) {
+ if (currentDepth > maxDepth) {
return;
}
- for ( Resource child : resource.getChildren() ) {
+ for (Resource child : resource.getChildren()) {
dumpRepo0(child.getPath(), maxDepth, currentDepth);
}
}