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())); } }