diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8aa0bcc95..56095859c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,8 @@ behavior.
- Added caliper content-package module structure to provide a common basis for oakpal self-test
functionality in the future. The intention is for it eventually to drive at least one example
of every event expressible to a ProgressCheck by the OakMachine.
+- Added subpackage and embedded package sorted installation similar to BEST_EFFORT DependencyHandling behavior
+to give some expectation and control of predictable install order.
## [2.1.0] - 2020-05-28
diff --git a/calipers/ui.apps.author/pom.xml b/calipers/ui.apps.author/pom.xml
index 5aabaed0a..efe218fff 100644
--- a/calipers/ui.apps.author/pom.xml
+++ b/calipers/ui.apps.author/pom.xml
@@ -61,6 +61,13 @@
none
+
+
+ net.adamcin.oakpal
+ oakpal-caliper.ui.apps
+ ${project.version}
+
+
net.adamcin.oakpal
@@ -72,6 +79,12 @@
+
+ net.adamcin.oakpal
+ oakpal-caliper.ui.apps
+ 2.2.0-SNAPSHOT
+ zip
+
net.adamcin.oakpal
oakpal-caliper.ui.apps.structure
diff --git a/calipers/ui.apps.publish/pom.xml b/calipers/ui.apps.publish/pom.xml
index 008884bc9..5668e0a19 100644
--- a/calipers/ui.apps.publish/pom.xml
+++ b/calipers/ui.apps.publish/pom.xml
@@ -61,6 +61,13 @@
none
+
+
+ net.adamcin.oakpal
+ oakpal-caliper.ui.apps
+ ${project.version}
+
+
net.adamcin.oakpal
@@ -72,6 +79,12 @@
+
+ net.adamcin.oakpal
+ oakpal-caliper.ui.apps
+ 2.2.0-SNAPSHOT
+ zip
+
net.adamcin.oakpal
oakpal-caliper.ui.apps.structure
diff --git a/calipers/ui.content.suba2/pom.xml b/calipers/ui.content.suba2/pom.xml
index d4a0a6717..d02e63730 100644
--- a/calipers/ui.content.suba2/pom.xml
+++ b/calipers/ui.content.suba2/pom.xml
@@ -62,6 +62,11 @@
oakpal-caliper.ui.apps
${project.version}
+
+ net.adamcin.oakpal
+ oakpal-caliper.ui.content.subc1
+ ${project.version}
+
@@ -74,5 +79,11 @@
2.2.0-SNAPSHOT
zip
+
+ net.adamcin.oakpal
+ oakpal-caliper.ui.content.subc1
+ 2.2.0-SNAPSHOT
+ zip
+
diff --git a/calipers/ui.content.subb3/pom.xml b/calipers/ui.content.subb3/pom.xml
index 1185cb50c..34e8f863b 100644
--- a/calipers/ui.content.subb3/pom.xml
+++ b/calipers/ui.content.subb3/pom.xml
@@ -62,6 +62,11 @@
oakpal-caliper.ui.apps
${project.version}
+
+ net.adamcin.oakpal
+ oakpal-caliper.ui.content.suba2
+ ${project.version}
+
@@ -74,5 +79,11 @@
2.2.0-SNAPSHOT
zip
+
+ net.adamcin.oakpal
+ oakpal-caliper.ui.content.suba2
+ 2.2.0-SNAPSHOT
+ zip
+
diff --git a/core/src/main/java/net/adamcin/oakpal/core/OakMachine.java b/core/src/main/java/net/adamcin/oakpal/core/OakMachine.java
index 87a738014..b18d646db 100644
--- a/core/src/main/java/net/adamcin/oakpal/core/OakMachine.java
+++ b/core/src/main/java/net/adamcin/oakpal/core/OakMachine.java
@@ -49,6 +49,7 @@
import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter;
import org.apache.jackrabbit.vault.fs.api.ProgressTrackerListener;
import org.apache.jackrabbit.vault.packaging.DependencyHandling;
+import org.apache.jackrabbit.vault.packaging.DependencyUtil;
import org.apache.jackrabbit.vault.packaging.InstallHookProcessorFactory;
import org.apache.jackrabbit.vault.packaging.JcrPackage;
import org.apache.jackrabbit.vault.packaging.JcrPackageManager;
@@ -80,6 +81,7 @@
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
@@ -816,24 +818,48 @@ private void processPackage(Session admin, JcrPackageManager manager, JcrPackage
admin.save();
final SubPackageHandling subPackageHandling = jcrPackage.getPackage().getSubPackageHandling();
- final List installableSubpacks = new ArrayList<>();
- final EnumSet installableOptions =
- EnumSet.complementOf(EnumSet.of(SubPackageHandling.Option.ADD, SubPackageHandling.Option.IGNORE));
- for (PackageId subpackId : subpacks) {
- final SubPackageHandling.Option option = subPackageHandling.getOption(subpackId);
- if (installableOptions.contains(option)) {
- installableSubpacks.add(subpackId);
- }
- }
jcrPackage.close();
propagateCheckPackageEvent(preInstall, packageId, handler -> handler.afterExtract(packageId, inspectSession));
- for (PackageId subpackId : installableSubpacks) {
- processSubpackage(admin, manager, subpackId, packageId,
- preInstall || subpackageSilencer.test(subpackId, packageId));
+ if (!subpacks.isEmpty()) {
+ final List installableSubpacks =
+ preprocessInstallableSubpackages(manager, packageId, subPackageHandling, subpacks);
+
+ for (PackageId subpackId : installableSubpacks) {
+ processSubpackage(admin, manager, subpackId, packageId,
+ preInstall || subpackageSilencer.test(subpackId, packageId));
+ }
+ }
+ }
+
+ List preprocessInstallableSubpackages(final JcrPackageManager manager,
+ final PackageId parentPackageId,
+ final SubPackageHandling subPackageHandling,
+ final List subpacks)
+ throws RepositoryException, IOException {
+ final List installSequence = new ArrayList<>();
+ final EnumSet installableOptions =
+ EnumSet.complementOf(EnumSet.of(SubPackageHandling.Option.ADD, SubPackageHandling.Option.IGNORE));
+
+ final List sortable = new LinkedList<>();
+ try {
+ for (PackageId packageId : subpacks) {
+ sortable.add(manager.open(packageId));
+ }
+ Fun.>resultNothing1(DependencyUtil::sortPackages).apply(sortable);
+ for (JcrPackage sorted : sortable) {
+ final PackageId sortedId = sorted.getPackage().getId();
+ final SubPackageHandling.Option option = subPackageHandling.getOption(sortedId);
+ if (installableOptions.contains(option)) {
+ installSequence.add(sortedId);
+ }
+ }
+ } finally {
+ sortable.forEach(JcrPackage::close);
}
+ return installSequence;
}
static Consumer
diff --git a/core/src/main/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulator.java b/core/src/main/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulator.java
index ab7bb6089..64b641185 100644
--- a/core/src/main/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulator.java
+++ b/core/src/main/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulator.java
@@ -26,6 +26,8 @@
import org.apache.felix.cm.file.ConfigurationHandler;
import org.apache.felix.cm.json.Configurations;
import org.apache.jackrabbit.commons.JcrUtils;
+import org.apache.jackrabbit.vault.packaging.CyclicDependencyException;
+import org.apache.jackrabbit.vault.packaging.DependencyUtil;
import org.apache.jackrabbit.vault.packaging.JcrPackage;
import org.apache.jackrabbit.vault.packaging.JcrPackageManager;
import org.apache.jackrabbit.vault.packaging.PackageId;
@@ -33,6 +35,8 @@
import org.apache.sling.installer.api.InstallableResource;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import javax.jcr.Node;
import javax.jcr.Property;
@@ -48,6 +52,7 @@
import java.io.Reader;
import java.lang.reflect.Array;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.Enumeration;
@@ -58,16 +63,19 @@
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
-import java.util.Queue;
import java.util.function.Function;
+import static net.adamcin.oakpal.api.Fun.inferTest1;
import static net.adamcin.oakpal.api.Fun.result0;
import static net.adamcin.oakpal.api.Fun.result1;
+import static net.adamcin.oakpal.api.Fun.toEntry;
/**
* Noop implementation of a SlingSimulator.
*/
public final class DefaultSlingSimulator implements SlingSimulatorBackend, SlingSimulator {
+ private static final Logger LOGGER = LoggerFactory.getLogger(DefaultSlingSimulator.class);
+
public static SlingSimulatorBackend instance() {
return new DefaultSlingSimulator();
}
@@ -77,7 +85,7 @@ public static SlingSimulatorBackend instance() {
private ErrorListener errorListener;
- private final Queue installables = new LinkedList<>();
+ private final LinkedList installables = new LinkedList<>();
@Override
public void startedScan() {
@@ -137,6 +145,13 @@ Optional createInstallableOrReport(final @NotNul
}
}
+ void internalAddInstallable(final @NotNull SlingInstallable installable) {
+ installables.removeIf(inferTest1(EmbeddedPackageInstallable.class::isInstance)
+ .and(registered -> installable.getJcrPath()
+ .equals(((EmbeddedPackageInstallable) registered).getJcrPath())));
+ installables.add(installable);
+ }
+
@Override
public @Nullable SlingInstallable addInstallableNode(final @NotNull PackageId parentPackageId,
final @NotNull Node node) {
@@ -151,11 +166,63 @@ Optional createInstallableOrReport(final @NotNul
.toOptional().flatMap(Function.identity())
.orElse(null);
if (installable != null) {
- installables.add(installable);
+ internalAddInstallable(installable);
+ }
+ if (installable instanceof EmbeddedPackageInstallable) {
+ Fun.resultNothing1(DefaultSlingSimulator::shufflePackagesByDependency).apply(this);
}
return installable;
}
+ LinkedList getInstallables() {
+ return installables;
+ }
+
+ void shufflePackagesByDependency() throws RepositoryException, CyclicDependencyException, IOException {
+ final Map originalLookup = getInstallables().stream()
+ .filter(EmbeddedPackageInstallable.class::isInstance)
+ .map(EmbeddedPackageInstallable.class::cast)
+ .map(inst -> toEntry(inst.getEmbeddedId(), inst))
+ .collect(Fun.entriesToMapOfType(LinkedHashMap::new, Fun.keepFirstMerger()));
+
+ final List originalOrder = new ArrayList<>(originalLookup.keySet());
+ final List sortedOrder = new ArrayList<>();
+
+ final List closeable = new LinkedList<>();
+ final List sortable = new LinkedList<>();
+ try {
+ for (PackageId key : originalOrder) {
+ final JcrPackage jcrPack = packageManager.open(session.getNode(originalLookup.get(key).getJcrPath()),
+ true);
+ closeable.add(jcrPack);
+ sortable.add(jcrPack.getPackage());
+ }
+ DependencyUtil.sort(sortable);
+ for (VaultPackage pack : sortable) {
+ sortedOrder.add(pack.getId());
+ }
+ } finally {
+ closeable.forEach(Fun.toVoid1(Fun.resultNothing1(JcrPackage::close)));
+ }
+
+ if (sortedOrder.size() == originalOrder.size() && !originalOrder.equals(sortedOrder)) {
+ for (int i = 0; i < originalOrder.size(); i++) {
+ if (!originalOrder.get(i).equals(sortedOrder.get(i))) {
+ for (PackageId key : sortedOrder.subList(i, sortedOrder.size())) {
+ final EmbeddedPackageInstallable installable = originalLookup.get(key);
+ if (getInstallables().removeIf(inferTest1(EmbeddedPackageInstallable.class::isInstance)
+ .and(registered ->
+ key.equals(((EmbeddedPackageInstallable) registered).getEmbeddedId())))) {
+
+ internalAddInstallable(installable);
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
static final String SLING_NS = "http://sling.apache.org/jcr/sling/1.0";
static final String SLING_OSGI_CONFIG = "{" + SLING_NS + "}OsgiConfig";
static final String JCR_CONTENT_DATA = "jcr:content/jcr:data";
diff --git a/core/src/test/java/net/adamcin/oakpal/core/OakMachineCaliperTest.java b/core/src/test/java/net/adamcin/oakpal/core/OakMachineCaliperTest.java
index 6fb231974..653d2f23b 100644
--- a/core/src/test/java/net/adamcin/oakpal/core/OakMachineCaliperTest.java
+++ b/core/src/test/java/net/adamcin/oakpal/core/OakMachineCaliperTest.java
@@ -143,7 +143,7 @@ public Collection getReportedViolations() {
.build()
.scanPackage(grandTourPackage);
- assertEquals("expect identified packages in set", new HashSet<>(installOrderFull),
- identifiedPackageIds.stream().map(PackageId::getName).collect(Collectors.toSet()));
+ assertEquals("expect identified packages in set", installOrderFull,
+ identifiedPackageIds.stream().map(PackageId::getName).collect(Collectors.toList()));
}
}