SLING-11203 Introduce requireImportProvider directive (#15)
A declared requireImportProvider directive so the author can ensure that
the files are processed as intended
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/ImportOptions.java b/src/main/java/org/apache/sling/jcr/contentloader/ImportOptions.java
index f66d680..4fc2e3e 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/ImportOptions.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/ImportOptions.java
@@ -18,6 +18,7 @@
*/
package org.apache.sling.jcr.contentloader;
+import org.jetbrains.annotations.NotNull;
import org.osgi.annotation.versioning.ConsumerType;
/**
@@ -86,4 +87,13 @@
*/
public abstract boolean isIgnoredImportProvider(String extension);
+ /**
+ * Check if the given entry name should require a matching registered
+ * import provider.
+ *
+ * @param name the entry name to check
+ * @return true to require an import provider, false otherwise
+ */
+ public abstract boolean isImportProviderRequired(@NotNull String name);
+
}
\ No newline at end of file
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 cae3970..527b63f 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/PathEntry.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/PathEntry.java
@@ -109,6 +109,14 @@
public static final String IGNORE_CONTENT_READERS_DIRECTIVE = "ignoreImportProviders";
/**
+ * The require content readers directive specifying which of the available
+ * {@link org.apache.sling.jcr.contentloader.ContentReader}s should exist before
+ * content loading. This is a string value that defaults to the emptystring.
+ * @since 2.5.2
+ */
+ public static final String REQUIRE_CONTENT_READERS_DIRECTIVE = "requireImportProviders";
+
+ /**
* The flag "maven:mount" is not actually used by the JCR Content Loader. It can be used
* to signal to the "fsmount" goal of the sling-maven-plugin to ignore a certain Sling-Initial-Content entry
* of a Maven project when "sling:mount" is executed on the command line.
@@ -128,6 +136,7 @@
CHECKIN_DIRECTIVE,
AUTOCHECKOUT_DIRECTIVE,
IGNORE_CONTENT_READERS_DIRECTIVE,
+ REQUIRE_CONTENT_READERS_DIRECTIVE,
MAVEN_MOUNT_DIRECTIVE
));
@@ -156,6 +165,9 @@
/** Which content readers should be ignored? @since 2.0.4 */
private final List<String> ignoreContentReaders;
+ /** Which content readers should be required? @since 2.5.2 */
+ private final List<String> requireContentReaders;
+
/**
* Target path where initial content will be loaded. If it´s null then
* target node is the root node
@@ -327,6 +339,16 @@
}
}
+ // expand directive
+ this.requireContentReaders = new ArrayList<>();
+ final String requireContentReadersValue = entry.getDirectiveValue(REQUIRE_CONTENT_READERS_DIRECTIVE);
+ if ( requireContentReadersValue != null && requireContentReadersValue.length() > 0 ) {
+ final StringTokenizer st = new StringTokenizer(requireContentReadersValue, ",");
+ while ( st.hasMoreTokens() ) {
+ this.requireContentReaders.add(st.nextToken());
+ }
+ }
+
// workspace directive
final String workspaceValue = entry.getDirectiveValue(WORKSPACE_DIRECTIVE);
if (pathValue != null) {
@@ -392,6 +414,42 @@
return new HashSet<>(ignoreContentReaders);
}
+ @Override
+ public boolean isImportProviderRequired(@NotNull String name) {
+ boolean required = false;
+
+ if (!this.requireContentReaders.isEmpty()) {
+ // a directive was supplied, so use a filter to check if the
+ // name ends with the suffix and is not listed in the ignored
+ // import provider set
+ required = this.requireContentReaders.stream()
+ .anyMatch(suffix ->
+ // verify the file suffix matches
+ hasNameSuffix(name, suffix) &&
+ // and not one of the ignored providers
+ !isIgnoredImportProvider(suffix));
+ }
+ return required;
+ }
+
+ /**
+ * Check if the name ends with the supplied suffix
+ *
+ * @param name the name to check
+ * @param suffix the suffix to check
+ * @return true if the name ends with the suffix
+ */
+ private boolean hasNameSuffix(String name, String suffix) {
+ // ensure neither arg is null
+ return name != null && suffix != null &&
+ // is longer than suffix
+ name.length() > suffix.length() &&
+ // ends with suffix
+ name.endsWith(suffix) &&
+ // dot before the suffix
+ '.' == name.charAt(name.length() - suffix.length() - 1);
+ }
+
public String getTarget() {
return target;
}
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 71bd020..acfce12 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
@@ -105,9 +105,28 @@
}
/**
+ * Retry loading bundles that have previously been delayed
+ * @param metadataSession the JCR Session for reading/writing metadata
+ */
+ public void retryDelayedBundles(final Session metadataSession) {
+ // 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();) {
+ Bundle delayed = di.next();
+ if (registerBundleInternal(metadataSession, delayed, true, false)) {
+ di.remove();
+ }
+ }
+ currentSize = delayedBundles.size();
+ }
+ }
+
+ /**
* Register a bundle and install its content.
*
- * @param metadataSession the JCR Session for reading/writing metadat
+ * @param metadataSession the JCR Session for reading/writing metadata
* @param bundle the bundle to install
*/
public void registerBundle(final Session metadataSession, final Bundle bundle, final boolean isUpdate) {
@@ -120,18 +139,7 @@
log.debug("Registering bundle {} for content loading.", bundle.getSymbolicName());
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();) {
- Bundle delayed = di.next();
- if (registerBundleInternal(metadataSession, delayed, true, false)) {
- di.remove();
- }
- }
- currentSize = delayedBundles.size();
- }
+ retryDelayedBundles(metadataSession);
} else if (!isUpdate) {
// add to delayed bundles - if this is not an update!
delayedBundles.add(bundle);
@@ -188,6 +196,12 @@
bundleHelper.unlockBundleContentInfo(metadataSession, bundle, success, createdNodes);
}
+ } catch (ContentReaderUnavailableException crue) {
+ // if we are retrying we already logged this message once, so we
+ // won't log it again
+ if (!isRetry) {
+ log.warn("Cannot load initial content for bundle {} : {}", bundle.getSymbolicName(), crue.getMessage());
+ }
} catch (RepositoryException re) {
// if we are retrying we already logged this message once, so we
// won't log it again
@@ -244,7 +258,7 @@
* @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 {
+ final Iterator<PathEntry> pathIter, final boolean contentAlreadyLoaded) throws RepositoryException, ContentReaderUnavailableException {
final List<String> createdNodes = new ArrayList<>();
final Map<String, Session> createdSessions = new HashMap<>();
@@ -352,7 +366,7 @@
*/
private void installFromPath(final Bundle bundle, final String path, final PathEntry configuration,
final Node parent, final List<String> createdNodes, final DefaultContentCreator contentCreator)
- throws RepositoryException {
+ throws RepositoryException, ContentReaderUnavailableException {
// init content creator
contentCreator.init(configuration, getContentReaders(), createdNodes, null);
@@ -434,7 +448,7 @@
*/
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 DefaultContentCreator contentCreator) throws RepositoryException, ContentReaderUnavailableException {
final URL file = bundle.getEntry(entry);
final String name = getName(entry);
@@ -466,7 +480,14 @@
log.warn("No node created for file {} {}", file, name);
}
} else {
- log.debug("Can't find content reader for entry {} at {}", entry, name);
+ // if we require a ContentReader for this entry but didn't find one
+ // then throw an exception to stop processing this bundle and put
+ // it into the delayedBundles list to retry later
+ if (configuration.isImportProviderRequired(name)) {
+ throw new ContentReaderUnavailableException(String.format("Unable to locate a required content reader for entry %s", entry));
+ } else {
+ log.debug("Can't find content reader for entry {} at {}", entry, name);
+ }
}
// otherwise just place as file
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 f00f231..09dfff0 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
@@ -64,7 +64,7 @@
Constants.SERVICE_DESCRIPTION
+ "=Apache Sling Content Loader Implementation" }, immediate = true, configurationPolicy = ConfigurationPolicy.OPTIONAL)
@Designate(ocd = BundleContentLoaderConfiguration.class, factory = false)
-public class BundleContentLoaderListener implements SynchronousBundleListener, BundleHelper {
+public class BundleContentLoaderListener implements SynchronousBundleListener, BundleHelper, ContentReaderWhiteboardListener {
public static final String PROPERTY_CONTENT_LOADED = "content-loaded";
public static final String PROPERTY_CONTENT_LOADED_AT = "content-load-time";
@@ -133,6 +133,26 @@
@Reference(target = "(component.name=org.apache.sling.jcr.contentloader.internal.readers.ZipReader)")
private ContentReader mandatoryContentReader4;
+ // ---------- ContentReaderWhiteboardListener -----------------------------------------------
+
+ /**
+ * When a new ContentReader component arrives, try to re-process any
+ * delayed bundles in case the new ContentReader makes it possible to
+ * process them now
+ */
+ @Override
+ public synchronized void handleContentReaderAdded(ContentReader operation) {
+ Session session = null;
+ try {
+ session = this.getSession();
+ bundleContentLoader.retryDelayedBundles(session);
+ } catch (Exception t) {
+ log.error("handleContentReaderAdded: Problem loading initial content of delayed bundles", t);
+ } finally {
+ this.ungetSession(session);
+ }
+ }
+
// ---------- BundleListener -----------------------------------------------
/**
@@ -240,6 +260,8 @@
this.bundleContentLoader = new BundleContentLoader(this, contentReaderWhiteboard, configuration);
bundleContext.addBundleListener(this);
+ // start listening for new ContentReader components
+ contentReaderWhiteboard.setListener(this);
Session session = null;
try {
@@ -289,6 +311,8 @@
@Deactivate
protected synchronized void deactivate(BundleContext bundleContext) {
bundleContext.removeBundleListener(this);
+ // stop listening for new ContentReader components
+ contentReaderWhiteboard.removeListener();
if (this.bundleContentLoader != null) {
this.bundleContentLoader.dispose();
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReaderUnavailableException.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReaderUnavailableException.java
new file mode 100644
index 0000000..0396d8e
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReaderUnavailableException.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;
+
+/**
+ * This exception is thrown when a required ContentReader is not yet
+ * available.
+ */
+class ContentReaderUnavailableException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public ContentReaderUnavailableException(String message) {
+ super(message);
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReaderWhiteboard.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReaderWhiteboard.java
index 3c6062e..0cbbac5 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReaderWhiteboard.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReaderWhiteboard.java
@@ -30,10 +30,19 @@
})
public class ContentReaderWhiteboard {
+ private ContentReaderWhiteboardListener listener;
+
private Map<String, ContentReader> readersByExtension = new LinkedHashMap<>();
private Map<String, ContentReader> readersByType = new LinkedHashMap<>();
+ public void setListener(ContentReaderWhiteboardListener listener) {
+ this.listener = listener;
+ }
+ public void removeListener() {
+ setListener(null);
+ }
+
public Map<String, ContentReader> getReadersByExtension() {
return readersByExtension;
}
@@ -61,6 +70,12 @@
}
}
}
+
+ // notify the listener that we have a new content reader
+ ContentReaderWhiteboardListener l = this.listener;
+ if (l != null) {
+ l.handleContentReaderAdded(operation);
+ }
}
protected void unbindContentReader(final Map<String, Object> properties) {
@@ -81,4 +96,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReaderWhiteboardListener.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReaderWhiteboardListener.java
new file mode 100644
index 0000000..f572501
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReaderWhiteboardListener.java
@@ -0,0 +1,26 @@
+/*
+ * 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.apache.sling.jcr.contentloader.ContentReader;
+
+/**
+ * Callback interface to allow the BundleContentLoaderListener to do work
+ * when a new ContentReader component arrives
+ */
+public interface ContentReaderWhiteboardListener {
+
+ void handleContentReaderAdded(final ContentReader operation);
+
+}
diff --git a/src/test/java/org/apache/sling/jcr/contentloader/ImportOptionsFactory.java b/src/test/java/org/apache/sling/jcr/contentloader/ImportOptionsFactory.java
index c1ab108..9708dcb 100644
--- a/src/test/java/org/apache/sling/jcr/contentloader/ImportOptionsFactory.java
+++ b/src/test/java/org/apache/sling/jcr/contentloader/ImportOptionsFactory.java
@@ -16,6 +16,8 @@
*/
package org.apache.sling.jcr.contentloader;
+import org.jetbrains.annotations.NotNull;
+
public final class ImportOptionsFactory {
public static final int NO_OPTIONS = 0;
@@ -33,8 +35,9 @@
public static final int IGNORE_IMPORT_PROVIDER = 0x1 << 5;
public static final int CHECK_IN = 0x1 << 6;
-
-
+
+ public static final int REQUIRE_IMPORT_PROVIDER = 0x1 << 7;
+
public static ImportOptions createImportOptions(int options){
return new ImportOptions() {
@Override
@@ -63,6 +66,11 @@
}
@Override
+ public boolean isImportProviderRequired(@NotNull String name) {
+ return (options & REQUIRE_IMPORT_PROVIDER) > NO_OPTIONS;
+ }
+
+ @Override
public boolean isPropertyMerge() {
return (options & SYNCH_PROPERTIES) > NO_OPTIONS;
}
diff --git a/src/test/java/org/apache/sling/jcr/contentloader/internal/SLING11203Test.java b/src/test/java/org/apache/sling/jcr/contentloader/internal/SLING11203Test.java
new file mode 100644
index 0000000..d06d281
--- /dev/null
+++ b/src/test/java/org/apache/sling/jcr/contentloader/internal/SLING11203Test.java
@@ -0,0 +1,170 @@
+/*
+ * 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 static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+
+import javax.jcr.Session;
+
+import org.apache.sling.jcr.contentloader.ContentReader;
+import org.apache.sling.jcr.contentloader.internal.readers.JsonReader;
+import org.apache.sling.jcr.contentloader.internal.readers.OrderedJsonReader;
+import org.apache.sling.jcr.contentloader.internal.readers.XmlReader;
+import org.apache.sling.jcr.contentloader.internal.readers.ZipReader;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.osgi.framework.Bundle;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Testing content loader waiting for required content reader
+ */
+public class SLING11203Test {
+
+ protected org.slf4j.Logger logger = LoggerFactory.getLogger(getClass());
+
+ @Rule
+ public final SlingContext context = new SlingContext(ResourceResolverType.JCR_OAK);
+
+ private BundleContentLoaderListener bundleHelper;
+
+ @Rule
+ public TestRule watcher = new TestWatcher() {
+
+ /* (non-Javadoc)
+ * @see org.junit.rules.TestWatcher#starting(org.junit.runner.Description)
+ */
+ @Override
+ protected void starting(Description description) {
+ logger.info("Starting test: {}", description.getMethodName());
+ }
+
+ /* (non-Javadoc)
+ * @see org.junit.rules.TestWatcher#finished(org.junit.runner.Description)
+ */
+ @Override
+ protected void finished(Description description) {
+ logger.info("Finished test: {}", description.getMethodName());
+ }
+
+ };
+
+ @Before
+ public void prepareContentLoader() throws Exception {
+ // NOTE: initially only the default set of content readers are registered
+ context.registerInjectActivateService(JsonReader.class);
+ context.registerInjectActivateService(OrderedJsonReader.class);
+ context.registerInjectActivateService(XmlReader.class);
+ context.registerInjectActivateService(ZipReader.class);
+
+ // whiteboard which holds readers
+ context.registerInjectActivateService(new ContentReaderWhiteboard());
+
+ // register the content loader service
+ bundleHelper = context.registerInjectActivateService(new BundleContentLoaderListener());
+
+ }
+
+ @Test
+ public void loadContentWithoutDirectiveExpectedContentReaderRegistered() throws Exception {
+ loadContentWithDirective();
+
+ // check node was not added during parsing the file
+ assertThat("Included resource should not have been imported", context.resourceResolver().getResource("/libs/app"), nullValue());
+ // check file was not loaded as non-parsed file
+ assertThat("Included resource should not have been imported", context.resourceResolver().getResource("/libs/app.sling11203"), nullValue());
+ }
+
+ @Test
+ public void loadContentWithDirectiveExpectedContentReaderRegisteredBeforeBundleLoaded() throws Exception {
+ // register the content reader that we require before registering the bundle
+ registerCustomContentReader();
+
+ loadContentWithDirective();
+
+ // check node was added during parsing the file
+ assertThat("Included resource should have been imported", context.resourceResolver().getResource("/libs/app"), notNullValue());
+ // check file was not loaded as non-parsed file
+ assertThat("Included resource should not have been imported", context.resourceResolver().getResource("/libs/app.sling11203"), nullValue());
+ }
+
+ @Test
+ public void loadContentWithDirectiveExpectedContentReaderRegisteredAfterBundleLoaded() throws Exception {
+ loadContentWithDirective();
+
+ // check node was not added during parsing the file
+ assertThat("Included resource should not have been imported", context.resourceResolver().getResource("/libs/app"), nullValue());
+ // check file was not loaded as non-parsed file
+ assertThat("Included resource should not have been imported", context.resourceResolver().getResource("/libs/app.sling11203"), nullValue());
+
+ // register the content reader that we require
+ registerCustomContentReader();
+
+ // check node was added during parsing the file
+ assertThat("Included resource should have been imported", context.resourceResolver().getResource("/libs/app"), notNullValue());
+ // check file was not loaded as non-parsed file
+ assertThat("Included resource should not have been imported", context.resourceResolver().getResource("/libs/app.sling11203"), nullValue());
+ }
+
+ protected void registerCustomContentReader() {
+ // register the content reader that we require after registering the bundle
+ // to trigger the retry
+ context.registerService(ContentReader.class, new SLING11203XmlReader(),
+ Collections.singletonMap(ContentReader.PROPERTY_EXTENSIONS, "sling11203"));
+ }
+
+ protected void loadContentWithDirective() throws Exception {
+ // dig the BundleContentLoader out of the component field so we get the
+ // same instance so the state for the retry logic is there
+ Field privateBundleContentLoaderField = BundleContentLoaderListener.class.getDeclaredField("bundleContentLoader");
+ privateBundleContentLoaderField.setAccessible(true);
+ BundleContentLoader contentLoader = (BundleContentLoader)privateBundleContentLoaderField.get(bundleHelper);
+
+ // requireImportProviders directive, so it should check if the specified
+ // required content reader is available
+ String initialContentHeader = "SLING-INF3/libs;path:=/libs;requireImportProviders:=sling11203";
+ Bundle mockBundle = BundleContentLoaderTest.newBundleWithInitialContent(context, initialContentHeader);
+
+ contentLoader.registerBundle(context.resourceResolver().adaptTo(Session.class), mockBundle, false);
+ }
+
+ /**
+ * A custom xml reader with a different file extension
+ */
+ public static class SLING11203XmlReader extends XmlReader {
+
+ private SLING11203XmlReader() {
+ super();
+ activate();
+ }
+
+ }
+
+}
diff --git a/src/test/resources/SLING-INF3/libs/app.sling11203 b/src/test/resources/SLING-INF3/libs/app.sling11203
new file mode 100644
index 0000000..b8d80c6
--- /dev/null
+++ b/src/test/resources/SLING-INF3/libs/app.sling11203
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<node>
+ <primaryNodeType>sling:Folder</primaryNodeType>
+</node>