This is an automated email from the ASF dual-hosted git repository.
cschneider pushed a commit to branch master
in repository
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-distribution-core.git
The following commit(s) were added to refs/heads/master by this push:
new 52f74897 SLING-13356 - Batch disposable package deletions in
ResourceDistributionPackageCleanup (#79)
52f74897 is described below
commit 52f74897fc982f991f81bf7c7a79d54f5a1939b7
Author: Christian Schneider <[email protected]>
AuthorDate: Sat Sep 26 20:28:53 2026 +0200
SLING-13356 - Batch disposable package deletions in
ResourceDistributionPackageCleanup (#79)
ResourceDistributionPackageCleanup previously deleted all disposable
packages
in a single JCR commit. On a resource-persisted distribution agent with a
large package backlog, this becomes an unbounded transaction that can
overwhelm the underlying repository.
Commits are now issued in bounded batches, tracked via a new
cleanupBatchSize constructor parameter / OSGi config attribute (default
100) exposed on both DistributionPackageBuilderFactory and
VaultDistributionPackageBuilderFactory. A value <= 0 preserves the
previous single-commit-per-run behavior. The existing two-argument
constructor is kept for backward compatibility and defaults to batching
disabled.
---
.../impl/ResourceDistributionPackageCleanup.java | 25 ++-
.../impl/DistributionPackageBuilderFactory.java | 14 +-
.../VaultDistributionPackageBuilderFactory.java | 14 +-
.../ResourceDistributionPackageCleanupTest.java | 247 +++++++++++++++++++++
.../DistributionPackageBuilderFactoryTest.java | 67 ++++++
...VaultDistributionPackageBuilderFactoryTest.java | 65 ++++++
6 files changed, 427 insertions(+), 5 deletions(-)
diff --git
a/src/main/java/org/apache/sling/distribution/packaging/impl/ResourceDistributionPackageCleanup.java
b/src/main/java/org/apache/sling/distribution/packaging/impl/ResourceDistributionPackageCleanup.java
index b51142c6..185f6a26 100644
---
a/src/main/java/org/apache/sling/distribution/packaging/impl/ResourceDistributionPackageCleanup.java
+++
b/src/main/java/org/apache/sling/distribution/packaging/impl/ResourceDistributionPackageCleanup.java
@@ -32,6 +32,9 @@ import org.slf4j.LoggerFactory;
/**
* This runnable removes unreferenced {@link ResourceDistributionPackage}
packages.
* It is meant to be run periodically. See SLING-6503.
+ * Deletions are committed in batches (see SLING-13356) rather than in a
single commit for
+ * the whole run, to avoid an unbounded transaction when a large number of
packages have
+ * accumulated.
*/
public class ResourceDistributionPackageCleanup implements Runnable {
@@ -44,18 +47,33 @@ public class ResourceDistributionPackageCleanup implements
Runnable {
private final ResourceResolverFactory resolverFactory;
+ /**
+ * Maximum number of disposable packages deleted per JCR commit during a
cleanup run.
+ * A value {@code <= 0} disables batching, restoring the previous behavior
of a single
+ * commit for the whole run.
+ */
+ private final int cleanupBatchSize;
+
public ResourceDistributionPackageCleanup(
@NotNull ResourceResolverFactory resolverFactory,
@NotNull ResourceDistributionPackageBuilder packageBuilder) {
+ this(resolverFactory, packageBuilder, 0);
+ }
+
+ public ResourceDistributionPackageCleanup(
+ @NotNull ResourceResolverFactory resolverFactory,
+ @NotNull ResourceDistributionPackageBuilder packageBuilder,
+ int cleanupBatchSize) {
this.resolverFactory = resolverFactory;
this.packageBuilder = packageBuilder;
+ this.cleanupBatchSize = cleanupBatchSize;
}
public void run() {
log.debug("Cleaning up {} packages", packageBuilder.getType());
ResourceResolver serviceResolver = null;
try {
- int deleted = 0, total = 0;
+ int deleted = 0, total = 0, pendingInBatch = 0;
serviceResolver = resolverFactory.getServiceResourceResolver(null);
for (Iterator<ResourceDistributionPackage> pkgs =
packageBuilder.getPackages(serviceResolver);
pkgs.hasNext();
@@ -65,6 +83,11 @@ public class ResourceDistributionPackageCleanup implements
Runnable {
log.debug("Delete package {}", pkg.getId());
deleted++;
pkg.delete(false);
+ pendingInBatch++;
+ if (cleanupBatchSize > 0 && pendingInBatch >=
cleanupBatchSize) {
+ serviceResolver.commit();
+ pendingInBatch = 0;
+ }
} else {
log.debug("package {} is not disposable", pkg.getId());
}
diff --git
a/src/main/java/org/apache/sling/distribution/serialization/impl/DistributionPackageBuilderFactory.java
b/src/main/java/org/apache/sling/distribution/serialization/impl/DistributionPackageBuilderFactory.java
index 96d5fc61..03ca7359 100644
---
a/src/main/java/org/apache/sling/distribution/serialization/impl/DistributionPackageBuilderFactory.java
+++
b/src/main/java/org/apache/sling/distribution/serialization/impl/DistributionPackageBuilderFactory.java
@@ -140,6 +140,14 @@ public class DistributionPackageBuilderFactory implements
DistributionPackageBui
+ "The delay between two runs of the cleanup
phase can be configured with this setting. 60 seconds by default")
long cleanupDelay() default DEFAULT_PACKAGE_CLEANUP_DELAY;
+ @AttributeDefinition(
+ name = "The number of disposable packages deleted per commit
during the cleanup phase.",
+ description = "The resource persisted packages are deleted in
batches of this size during each cleanup "
+ + "run, rather than in a single commit for the whole
run, to avoid an unbounded "
+ + "transaction when a large number of packages have
accumulated. A value <= 0 "
+ + "restores the previous behavior of a single commit
per cleanup run. 100 by default")
+ int cleanupBatchSize() default DEFAULT_PACKAGE_CLEANUP_BATCH_SIZE;
+
@AttributeDefinition(
name = "Package Node Filters",
description = "The package node path filters. Filter format:
path|+include|-exclude",
@@ -170,6 +178,7 @@ public class DistributionPackageBuilderFactory implements
DistributionPackageBui
private static final String DEFAULT_DIGEST_ALGORITHM = "NONE";
private static final int DEFAULT_MONITORING_QUEUE_SIZE = 0;
private static final long DEFAULT_PACKAGE_CLEANUP_DELAY = 60L;
+ private static final int DEFAULT_PACKAGE_CLEANUP_BATCH_SIZE = 100;
@Activate
public void activate(BundleContext context, Config conf) {
@@ -180,6 +189,7 @@ public class DistributionPackageBuilderFactory implements
DistributionPackageBui
String tempFsFolder =
SettingsUtils.removeEmptyEntry(conf.tempFsFolder());
String digestAlgorithm = conf.digestAlgorithm();
long cleanupDelay = conf.cleanupDelay();
+ int cleanupBatchSize = conf.cleanupBatchSize();
if (DEFAULT_DIGEST_ALGORITHM.equals(digestAlgorithm)) {
digestAlgorithm = null;
}
@@ -212,8 +222,8 @@ public class DistributionPackageBuilderFactory implements
DistributionPackageBui
digestAlgorithm,
nodeFilters,
propertyFilters);
- Runnable cleanup =
- new ResourceDistributionPackageCleanup(resolverFactory,
resourceDistributionPackageBuilder);
+ Runnable cleanup = new ResourceDistributionPackageCleanup(
+ resolverFactory, resourceDistributionPackageBuilder,
cleanupBatchSize);
Dictionary<String, Object> props = new Hashtable<String, Object>();
props.put(Scheduler.PROPERTY_SCHEDULER_CONCURRENT, false);
props.put(Scheduler.PROPERTY_SCHEDULER_PERIOD, cleanupDelay);
diff --git
a/src/main/java/org/apache/sling/distribution/serialization/impl/vlt/VaultDistributionPackageBuilderFactory.java
b/src/main/java/org/apache/sling/distribution/serialization/impl/vlt/VaultDistributionPackageBuilderFactory.java
index e3c6695d..aa5d5a52 100644
---
a/src/main/java/org/apache/sling/distribution/serialization/impl/vlt/VaultDistributionPackageBuilderFactory.java
+++
b/src/main/java/org/apache/sling/distribution/serialization/impl/vlt/VaultDistributionPackageBuilderFactory.java
@@ -135,6 +135,14 @@ public class VaultDistributionPackageBuilderFactory
implements DistributionPacka
+ "The delay between two runs of the cleanup
phase can be configured with this setting. 60 seconds by default")
long cleanupDelay() default DEFAULT_PACKAGE_CLEANUP_DELAY;
+ @AttributeDefinition(
+ name = "The number of disposable packages deleted per commit
during the cleanup phase.",
+ description = "The resource persisted packages are deleted in
batches of this size during each cleanup "
+ + "run, rather than in a single commit for the whole
run, to avoid an unbounded "
+ + "transaction when a large number of packages have
accumulated. A value <= 0 "
+ + "restores the previous behavior of a single commit
per cleanup run. 100 by default")
+ int cleanupBatchSize() default DEFAULT_PACKAGE_CLEANUP_BATCH_SIZE;
+
@AttributeDefinition(
name = "File threshold (in bytes)",
description =
@@ -202,6 +210,7 @@ public class VaultDistributionPackageBuilderFactory
implements DistributionPacka
}
private static final long DEFAULT_PACKAGE_CLEANUP_DELAY = 60L;
+ private static final int DEFAULT_PACKAGE_CLEANUP_BATCH_SIZE = 100;
// 1M
private static final int DEFAULT_FILE_THRESHOLD_VALUE = 1;
private static final String DEFAULT_MEMORY_UNIT = "MEGA_BYTES";
@@ -236,6 +245,7 @@ public class VaultDistributionPackageBuilderFactory
implements DistributionPacka
String[] packagePropertyFilters =
SettingsUtils.removeEmptyEntries(conf.property_filters());
long cleanupDelay = conf.cleanupDelay();
+ int cleanupBatchSize = conf.cleanupBatchSize();
String tempFsFolder =
SettingsUtils.removeEmptyEntry(conf.tempFsFolder());
boolean useBinaryReferences = conf.useBinaryReferences();
@@ -315,8 +325,8 @@ public class VaultDistributionPackageBuilderFactory
implements DistributionPacka
digestAlgorithm,
packageNodeFilters,
packagePropertyFilters);
- Runnable cleanup =
- new ResourceDistributionPackageCleanup(resolverFactory,
resourceDistributionPackageBuilder);
+ Runnable cleanup = new ResourceDistributionPackageCleanup(
+ resolverFactory, resourceDistributionPackageBuilder,
cleanupBatchSize);
Dictionary<String, Object> props = new Hashtable<String, Object>();
props.put(Scheduler.PROPERTY_SCHEDULER_CONCURRENT, false);
props.put(Scheduler.PROPERTY_SCHEDULER_PERIOD, cleanupDelay);
diff --git
a/src/test/java/org/apache/sling/distribution/packaging/impl/ResourceDistributionPackageCleanupTest.java
b/src/test/java/org/apache/sling/distribution/packaging/impl/ResourceDistributionPackageCleanupTest.java
new file mode 100644
index 00000000..8e97a560
--- /dev/null
+++
b/src/test/java/org/apache/sling/distribution/packaging/impl/ResourceDistributionPackageCleanupTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.distribution.packaging.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Iterator;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.distribution.DistributionRequest;
+import org.apache.sling.distribution.common.DistributionException;
+import org.apache.sling.distribution.packaging.DistributionPackage;
+import
org.apache.sling.distribution.serialization.DistributionContentSerializer;
+import org.apache.sling.distribution.serialization.DistributionExportOptions;
+import
org.apache.sling.distribution.util.impl.FileBackedMemoryOutputStream.MemoryUnit;
+import org.apache.sling.testing.mock.osgi.MockOsgi;
+import org.apache.sling.testing.mock.sling.MockSling;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class ResourceDistributionPackageCleanupTest {
+
+ private BundleContext bundleContext;
+ private ResourceResolver resolver;
+ private ResourceDistributionPackageBuilder builder;
+
+ @Before
+ public void setUp() {
+ bundleContext = MockOsgi.newBundleContext();
+ MockSling.setAdapterManagerBundleContext(bundleContext);
+ resolver =
MockSling.newResourceResolver(ResourceResolverType.JCR_MOCK, bundleContext);
+ builder = new ResourceDistributionPackageBuilder(
+ "test",
+ new TestSerializer(),
+ null,
+ 0,
+ MemoryUnit.valueOf("MEGA_BYTES"),
+ false,
+ null,
+ new String[0],
+ new String[0]);
+ }
+
+ @After
+ public void tearDown() {
+ if (resolver.isLive()) {
+ resolver.close();
+ }
+ MockSling.clearAdapterManagerBundleContext();
+ }
+
+ @Test
+ public void testAllDisposablePackagesAreDeletedInBatches() throws
Exception {
+ int total = 7;
+ for (int i = 0; i < total; i++) {
+ createDisposablePackage();
+ }
+ assertEquals(total, countPackages(resolver));
+
+ ResourceResolver serviceResolver = Mockito.spy(resolver);
+ ResourceResolverFactory resolverFactory =
mockResolverFactory(serviceResolver);
+ ResourceDistributionPackageCleanup cleanup =
+ new ResourceDistributionPackageCleanup(resolverFactory,
builder, 3);
+ cleanup.run();
+
+ assertEquals("all disposable packages should be deleted", 0,
countPackages(newVerificationResolver()));
+ // ceil(7 / 3) = 3 commits: after the 3rd and 6th deletions, plus a
final commit for the
+ // 7th (the "if hasChanges()" fallback after the loop).
+ verify(serviceResolver, times(3)).commit();
+ }
+
+ @Test
+ public void testNonDisposablePackagesAreLeftAlone() throws Exception {
+ createDisposablePackage();
+ createDisposablePackage();
+ createNonDisposablePackage();
+ assertEquals(3, countPackages(resolver));
+
+ ResourceResolverFactory resolverFactory =
mockResolverFactory(resolver);
+ ResourceDistributionPackageCleanup cleanup =
+ new ResourceDistributionPackageCleanup(resolverFactory,
builder, 100);
+ cleanup.run();
+
+ assertEquals("only the non-disposable package should remain", 1,
countPackages(newVerificationResolver()));
+ }
+
+ @Test
+ public void testExactMultipleOfBatchSizeCommitsOnlyOncePerBatch() throws
Exception {
+ int total = 6;
+ for (int i = 0; i < total; i++) {
+ createDisposablePackage();
+ }
+
+ ResourceResolver serviceResolver = Mockito.spy(resolver);
+ ResourceResolverFactory resolverFactory =
mockResolverFactory(serviceResolver);
+ ResourceDistributionPackageCleanup cleanup =
+ new ResourceDistributionPackageCleanup(resolverFactory,
builder, 3);
+ cleanup.run();
+
+ assertEquals(0, countPackages(newVerificationResolver()));
+ // 6 / 3 = exactly 2 commits, no extra empty commit at the end since
nothing is pending.
+ verify(serviceResolver, times(2)).commit();
+ }
+
+ @Test
+ public void testNonPositiveBatchSizeFallsBackToSingleCommit() throws
Exception {
+ int total = 5;
+ for (int i = 0; i < total; i++) {
+ createDisposablePackage();
+ }
+
+ ResourceResolver serviceResolver = Mockito.spy(resolver);
+ ResourceResolverFactory resolverFactory =
mockResolverFactory(serviceResolver);
+ ResourceDistributionPackageCleanup cleanup =
+ new ResourceDistributionPackageCleanup(resolverFactory,
builder, 0);
+ cleanup.run();
+
+ assertEquals(0, countPackages(newVerificationResolver()));
+ verify(serviceResolver, times(1)).commit();
+ }
+
+ @Test
+ public void testDeprecatedTwoArgConstructorFallsBackToSingleCommit()
throws Exception {
+ int total = 4;
+ for (int i = 0; i < total; i++) {
+ createDisposablePackage();
+ }
+
+ ResourceResolver serviceResolver = Mockito.spy(resolver);
+ ResourceResolverFactory resolverFactory =
mockResolverFactory(serviceResolver);
+ ResourceDistributionPackageCleanup cleanup = new
ResourceDistributionPackageCleanup(resolverFactory, builder);
+ cleanup.run();
+
+ assertEquals(0, countPackages(newVerificationResolver()));
+ verify(serviceResolver, times(1)).commit();
+ }
+
+ private ResourceResolverFactory mockResolverFactory(ResourceResolver
serviceResolver) throws LoginException {
+ ResourceResolverFactory resolverFactory =
mock(ResourceResolverFactory.class);
+
when(resolverFactory.getServiceResourceResolver(null)).thenReturn(serviceResolver);
+ return resolverFactory;
+ }
+
+ /**
+ * {@code cleanup.run()} closes the resolver it was given (per its own
{@code finally} block),
+ * so post-run assertions need a fresh resolver against the same
underlying (JCR_MOCK) repository
+ * rather than reusing the one passed to the cleanup run. {@code
MockSling.newResourceResolver()}
+ * can't be called a second time on the same bundle context (it tries to
register a second
+ * {@code ResourceResolverFactory} service) -- fetch the one already
registered in {@link #setUp()}
+ * instead.
+ */
+ private ResourceResolver newVerificationResolver() throws LoginException {
+ ServiceReference<ResourceResolverFactory> ref =
+
bundleContext.getServiceReference(ResourceResolverFactory.class);
+ ResourceResolverFactory factory = bundleContext.getService(ref);
+ return factory.getAdministrativeResourceResolver(null);
+ }
+
+ private void createDisposablePackage() throws DistributionException,
IOException {
+ ResourceDistributionPackage pkg = createPackage();
+ pkg.acquire("holder");
+ pkg.release("holder");
+ }
+
+ private void createNonDisposablePackage() throws DistributionException,
IOException {
+ ResourceDistributionPackage pkg = createPackage();
+ pkg.acquire("holder");
+ // never released -> not disposable
+ }
+
+ private ResourceDistributionPackage createPackage() throws
DistributionException, IOException {
+ DistributionRequest mockRequest = mock(DistributionRequest.class);
+ String path = "/content/" + java.util.UUID.randomUUID();
+ when(mockRequest.getPaths()).thenReturn(new String[] {path});
+ when(mockRequest.isDeep(path)).thenReturn(false);
+ DistributionPackage pkg = builder.createPackageForAdd(resolver,
mockRequest);
+ return (ResourceDistributionPackage) pkg;
+ }
+
+ private int countPackages(ResourceResolver withResolver) throws
DistributionException {
+ int count = 0;
+ for (Iterator<ResourceDistributionPackage> it =
builder.getPackages(withResolver); it.hasNext(); it.next()) {
+ count++;
+ }
+ return count;
+ }
+
+ private static class TestSerializer implements
DistributionContentSerializer {
+
+ @Override
+ public void exportToStream(
+ ResourceResolver resourceResolver, DistributionExportOptions
exportOptions, OutputStream outputStream)
+ throws DistributionException {
+ try {
+ outputStream.write("test".getBytes());
+ } catch (IOException ex) {
+ throw new DistributionException(ex);
+ }
+ }
+
+ @Override
+ public void importFromStream(ResourceResolver resourceResolver,
InputStream inputStream)
+ throws DistributionException {
+ throw new DistributionException("unsupported");
+ }
+
+ @Override
+ public String getName() {
+ return "test";
+ }
+
+ @Override
+ public boolean isRequestFiltering() {
+ return true;
+ }
+ }
+}
diff --git
a/src/test/java/org/apache/sling/distribution/serialization/impl/DistributionPackageBuilderFactoryTest.java
b/src/test/java/org/apache/sling/distribution/serialization/impl/DistributionPackageBuilderFactoryTest.java
new file mode 100644
index 00000000..395646f0
--- /dev/null
+++
b/src/test/java/org/apache/sling/distribution/serialization/impl/DistributionPackageBuilderFactoryTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.distribution.serialization.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import
org.apache.sling.distribution.serialization.DistributionContentSerializer;
+import org.apache.sling.testing.mock.osgi.MockOsgi;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class DistributionPackageBuilderFactoryTest {
+
+ private BundleContext bundleContext;
+
+ @Before
+ public void setUp() {
+ bundleContext = MockOsgi.newBundleContext();
+
+ DistributionContentSerializer serializer =
mock(DistributionContentSerializer.class);
+ when(serializer.getName()).thenReturn("test");
+ bundleContext.registerService(DistributionContentSerializer.class,
serializer, null);
+
+ ResourceResolverFactory resolverFactory =
mock(ResourceResolverFactory.class);
+ bundleContext.registerService(ResourceResolverFactory.class,
resolverFactory, null);
+ }
+
+ @Test
+ public void
testActivateWithResourcePersistenceRegistersCleanupWithConfiguredBatchSize() {
+ DistributionPackageBuilderFactory factory = new
DistributionPackageBuilderFactory();
+
+ Map<String, Object> config = new HashMap<>();
+ config.put("name", "test");
+ config.put("tempFsFolder", "target/tmp");
+ config.put("cleanupBatchSize", 42);
+
+ MockOsgi.injectServices(factory, bundleContext);
+ MockOsgi.activate(factory, bundleContext, config);
+
+ ServiceReference<Runnable> cleanupRef =
bundleContext.getServiceReference(Runnable.class);
+ assertNotNull("cleanup Runnable should be registered for the (default)
resource persistence type", cleanupRef);
+ }
+}
diff --git
a/src/test/java/org/apache/sling/distribution/serialization/impl/vlt/VaultDistributionPackageBuilderFactoryTest.java
b/src/test/java/org/apache/sling/distribution/serialization/impl/vlt/VaultDistributionPackageBuilderFactoryTest.java
new file mode 100644
index 00000000..25f266cd
--- /dev/null
+++
b/src/test/java/org/apache/sling/distribution/serialization/impl/vlt/VaultDistributionPackageBuilderFactoryTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.distribution.serialization.impl.vlt;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.jackrabbit.vault.packaging.Packaging;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.testing.mock.osgi.MockOsgi;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+
+public class VaultDistributionPackageBuilderFactoryTest {
+
+ private BundleContext bundleContext;
+
+ @Before
+ public void setUp() {
+ bundleContext = MockOsgi.newBundleContext();
+
+ bundleContext.registerService(Packaging.class, mock(Packaging.class),
null);
+
+ ResourceResolverFactory resolverFactory =
mock(ResourceResolverFactory.class);
+ bundleContext.registerService(ResourceResolverFactory.class,
resolverFactory, null);
+ }
+
+ @Test
+ public void
testActivateWithDefaultJcrvltTypeRegistersCleanupWithConfiguredBatchSize() {
+ VaultDistributionPackageBuilderFactory factory = new
VaultDistributionPackageBuilderFactory();
+
+ Map<String, Object> config = new HashMap<>();
+ config.put("name", "test");
+ config.put("tempFsFolder", "target/tmp");
+ config.put("cleanupBatchSize", 42);
+
+ MockOsgi.injectServices(factory, bundleContext);
+ MockOsgi.activate(factory, bundleContext, config);
+
+ ServiceReference<Runnable> cleanupRef =
bundleContext.getServiceReference(Runnable.class);
+ assertNotNull(
+ "cleanup Runnable should be registered for the (default)
jcrvlt/resource persistence type", cleanupRef);
+ }
+}