From c2617b6cf024ff64de7004dd34009bb7562b83b6 Mon Sep 17 00:00:00 2001 From: Mark Adamcin Date: Fri, 8 May 2020 14:33:26 -0700 Subject: [PATCH 01/10] add JcrInstallWatcher interface with hooks in OakMachine. --- .../api/EmbeddedPackageInstallable.java | 78 ++++ .../oakpal/api/OsgiBundleInstallable.java | 83 ++++ .../oakpal/api/OsgiConfigInstallable.java | 75 ++++ .../net/adamcin/oakpal/api/ProgressCheck.java | 82 ++++ .../api/RepoInitScriptsInstallable.java | 64 +++ .../adamcin/oakpal/api/SilenceableCheck.java | 36 ++ .../adamcin/oakpal/api/SlingInstallable.java | 62 +++ .../adamcin/oakpal/api/SlingSimulator.java | 56 +++ .../adamcin/oakpal/api/ProgressCheckTest.java | 44 +- core/pom.xml | 26 +- .../oakpal/core/DefaultErrorListener.java | 84 ++-- .../adamcin/oakpal/core/ErrorListener.java | 71 +++- .../net/adamcin/oakpal/core/OakMachine.java | 327 ++++++++++----- .../net/adamcin/oakpal/core/OakpalPlan.java | 25 +- .../oakpal/core/ProgressCheckAliasFacade.java | 62 ++- .../oakpal/core/ScriptProgressCheck.java | 70 +++- .../oakpal/core/SilencingCheckFacade.java | 185 +++++++++ .../oakpal/core/checks/SlingJcrInstaller.java | 101 +++++ .../core/sling/DefaultSlingSimulator.java | 336 +++++++++++++++ .../EmbeddedPackageInstallableParams.java | 35 ++ .../oakpal/core/sling/InternalResource.java | 20 + .../oakpal/core/sling/NoopSlingSimulator.java | 86 ++++ .../sling/OsgiBundleInstallableParams.java | 51 +++ .../sling/OsgiConfigInstallableParams.java | 46 +++ .../core/sling/SlingInstallableParams.java | 31 ++ .../core/sling/SlingSimulatorBackend.java | 88 ++++ .../oakpal/core/ErrorListenerTest.java | 2 + .../adamcin/oakpal/core/OakMachineTest.java | 384 +++++++++++++++++- .../oakpal/core/ScriptProgressCheckTest.java | 31 +- .../core/sling/DefaultSlingSimulatorTest.java | 70 ++++ .../sling/EmbeddedPackageInstallableTest.java | 38 ++ .../core/sling/NoopSlingSimulatorTest.java | 58 +++ .../sling/RepoInitScriptsInstallableTest.java | 43 ++ 33 files changed, 2649 insertions(+), 201 deletions(-) create mode 100644 api/src/main/java/net/adamcin/oakpal/api/EmbeddedPackageInstallable.java create mode 100644 api/src/main/java/net/adamcin/oakpal/api/OsgiBundleInstallable.java create mode 100644 api/src/main/java/net/adamcin/oakpal/api/OsgiConfigInstallable.java create mode 100644 api/src/main/java/net/adamcin/oakpal/api/RepoInitScriptsInstallable.java create mode 100644 api/src/main/java/net/adamcin/oakpal/api/SilenceableCheck.java create mode 100644 api/src/main/java/net/adamcin/oakpal/api/SlingInstallable.java create mode 100644 api/src/main/java/net/adamcin/oakpal/api/SlingSimulator.java create mode 100644 core/src/main/java/net/adamcin/oakpal/core/SilencingCheckFacade.java create mode 100644 core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java create mode 100644 core/src/main/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulator.java create mode 100644 core/src/main/java/net/adamcin/oakpal/core/sling/EmbeddedPackageInstallableParams.java create mode 100644 core/src/main/java/net/adamcin/oakpal/core/sling/InternalResource.java create mode 100644 core/src/main/java/net/adamcin/oakpal/core/sling/NoopSlingSimulator.java create mode 100644 core/src/main/java/net/adamcin/oakpal/core/sling/OsgiBundleInstallableParams.java create mode 100644 core/src/main/java/net/adamcin/oakpal/core/sling/OsgiConfigInstallableParams.java create mode 100644 core/src/main/java/net/adamcin/oakpal/core/sling/SlingInstallableParams.java create mode 100644 core/src/main/java/net/adamcin/oakpal/core/sling/SlingSimulatorBackend.java create mode 100644 core/src/test/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulatorTest.java create mode 100644 core/src/test/java/net/adamcin/oakpal/core/sling/EmbeddedPackageInstallableTest.java create mode 100644 core/src/test/java/net/adamcin/oakpal/core/sling/NoopSlingSimulatorTest.java create mode 100644 core/src/test/java/net/adamcin/oakpal/core/sling/RepoInitScriptsInstallableTest.java diff --git a/api/src/main/java/net/adamcin/oakpal/api/EmbeddedPackageInstallable.java b/api/src/main/java/net/adamcin/oakpal/api/EmbeddedPackageInstallable.java new file mode 100644 index 000000000..7230add18 --- /dev/null +++ b/api/src/main/java/net/adamcin/oakpal/api/EmbeddedPackageInstallable.java @@ -0,0 +1,78 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.api; + +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * An installable path identified as an embedded package. + */ +public final class EmbeddedPackageInstallable implements SlingInstallable { + private final @NotNull PackageId parentId; + private final @NotNull String jcrPath; + private final @NotNull PackageId embeddedId; + + /** + * Constructor. + * + * @param parentId the parent package id + * @param jcrPath the embedded jcr path + * @param embeddedId the embedded package id + */ + public EmbeddedPackageInstallable(final @NotNull PackageId parentId, + final @NotNull String jcrPath, + final @NotNull PackageId embeddedId) { + this.parentId = parentId; + this.jcrPath = jcrPath; + this.embeddedId = embeddedId; + } + + @NotNull + @Override + public PackageId getParentId() { + return parentId; + } + + @NotNull + @Override + public String getJcrPath() { + return jcrPath; + } + + @NotNull + public PackageId getEmbeddedId() { + return embeddedId; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final EmbeddedPackageInstallable that = (EmbeddedPackageInstallable) o; + return parentId.equals(that.parentId) && + jcrPath.equals(that.jcrPath) && + embeddedId.equals(that.embeddedId); + } + + @Override + public int hashCode() { + return Objects.hash(parentId, jcrPath, embeddedId); + } +} diff --git a/api/src/main/java/net/adamcin/oakpal/api/OsgiBundleInstallable.java b/api/src/main/java/net/adamcin/oakpal/api/OsgiBundleInstallable.java new file mode 100644 index 000000000..58d6f040e --- /dev/null +++ b/api/src/main/java/net/adamcin/oakpal/api/OsgiBundleInstallable.java @@ -0,0 +1,83 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.api; + +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.jar.Manifest; + +public final class OsgiBundleInstallable implements SlingInstallable { + private final @NotNull PackageId parentId; + private final @NotNull String jcrPath; + private final @Nullable String installationHint; + private final @NotNull Manifest manifest; + private final @NotNull String symbolicName; + private final @NotNull String version; + private final @Nullable String activationPolicy; + + public OsgiBundleInstallable(@NotNull final PackageId parentId, + @NotNull final String jcrPath, + @Nullable final String installationHint, + @NotNull final Manifest manifest, + @NotNull final String symbolicName, + @NotNull final String version, + @Nullable final String activationPolicy) { + this.parentId = parentId; + this.jcrPath = jcrPath; + this.installationHint = installationHint; + this.manifest = manifest; + this.symbolicName = symbolicName; + this.version = version; + this.activationPolicy = activationPolicy; + } + + @NotNull + @Override + public PackageId getParentId() { + return parentId; + } + + @NotNull + @Override + public String getJcrPath() { + return jcrPath; + } + + @Nullable + @Override + public String getInstallationHint() { + return installationHint; + } + + public Manifest getManifest() { + return (Manifest) manifest.clone(); + } + + public String getSymbolicName() { + return symbolicName; + } + + public String getVersion() { + return version; + } + + public String getActivationPolicy() { + return activationPolicy; + } +} diff --git a/api/src/main/java/net/adamcin/oakpal/api/OsgiConfigInstallable.java b/api/src/main/java/net/adamcin/oakpal/api/OsgiConfigInstallable.java new file mode 100644 index 000000000..9bf2e21b7 --- /dev/null +++ b/api/src/main/java/net/adamcin/oakpal/api/OsgiConfigInstallable.java @@ -0,0 +1,75 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.api; + +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Sling Installable representing an OSGi config node. + */ +public final class OsgiConfigInstallable implements SlingInstallable { + private final @NotNull PackageId parentId; + private final @NotNull String jcrPath; + private final @NotNull Map properties; + private final @NotNull String servicePid; + private final @Nullable String factoryPid; + + public OsgiConfigInstallable(@NotNull final PackageId parentId, + @NotNull final String jcrPath, + @NotNull final Map properties, + @NotNull final String servicePid, + @Nullable final String factoryPid) { + this.parentId = parentId; + this.jcrPath = jcrPath; + this.properties = Collections.unmodifiableMap(new HashMap<>(properties)); + this.servicePid = servicePid; + this.factoryPid = factoryPid; + } + + @NotNull + @Override + public PackageId getParentId() { + return parentId; + } + + @NotNull + @Override + public String getJcrPath() { + return jcrPath; + } + + @NotNull + public Map getProperties() { + return properties; + } + + @NotNull + public String getServicePid() { + return servicePid; + } + + @Nullable + public String getFactoryPid() { + return factoryPid; + } +} diff --git a/api/src/main/java/net/adamcin/oakpal/api/ProgressCheck.java b/api/src/main/java/net/adamcin/oakpal/api/ProgressCheck.java index 71c8dda91..22bc4231b 100644 --- a/api/src/main/java/net/adamcin/oakpal/api/ProgressCheck.java +++ b/api/src/main/java/net/adamcin/oakpal/api/ProgressCheck.java @@ -26,6 +26,7 @@ import javax.jcr.Session; import java.io.File; import java.util.List; +import java.util.Set; import java.util.jar.Manifest; /** @@ -55,6 +56,7 @@ default String getCheckName() { return getClass().getSimpleName(); } + /** * Called after the package is uploaded to the package manager at the beginning of the scan. Track subsequent * events using the package ID provided to this method. This method will only be called once for each package @@ -77,6 +79,7 @@ default void identifySubpackage(PackageId packageId, PackageId parentId) { } + /** * If the package provides a {@link Manifest}, it will be provided to the check using this method, prior to calling * {@link #beforeExtract(PackageId, Session, PackageProperties, MetaInf, List)}. @@ -154,4 +157,83 @@ default void deletedPath(PackageId packageId, String path, Session inspectSessio default void afterExtract(PackageId packageId, Session inspectSession) throws RepositoryException { } + + /** + * Override this method to accept an {@link SlingSimulator} to request installation of JCR resources like + * FileVault packages and RepositoryInitializer factory configs, as if running within a Sling repository instance. + *

+ * Also provied are the set of simulated Sling Run Modes. These are intended to drive construction of JCR path + * patterns to select embedded resources for installation upon receiving a matching + * {@link ProgressCheck#importedPath(PackageId, String, Node, PathAction)} event, but these run modes may also be + * used as a global configuration hint for progress checks that support modal behavior outside of their explicit + * JSON config format. NOTE: this set will always be empty by default, and must be populated in the plan or + * overridden at runtime in the execution layer. + * + * @param slingSimulator the sling simulator + * @param runModes the simulated sling run modes + * @since 2.1.0 + */ + default void simulateSling(SlingSimulator slingSimulator, Set runModes) { + + } + + /** + * Called after each embedded package is opened, if it has been submitted to the {@link SlingSimulator}. Track + * subsequent events using the package ID provided to this method. Conceptually, at least for the purposes of + * enforcing acceptance criteria against packaged JCR content, this is analogous to + * {@link #identifySubpackage(PackageId, PackageId)}. + * + * @param packageId the package ID of the newly opened embeddedPackage + * @param parentId the package ID of the parent package. + * @param jcrPath the JCR path of this embedded package within the repository + * @since 2.1.0 + */ + default void identifyEmbeddedPackage(PackageId packageId, PackageId parentId, String jcrPath) { + + } + + /** + * Provides an opportunity to inspect repository state before installing a resource submitted to the + * {@link SlingSimulator}. + * + * @param lastPackage the last preinstall or scan package + * @param slingInstallable the sling installable + * @param inspectSession session providing access to repository state + * @throws RepositoryException because of access to a {@link Session} + * @since 2.1.0 + */ + default void beforeSlingInstall(PackageId lastPackage, SlingInstallable slingInstallable, Session inspectSession) + throws RepositoryException { + + } + + /** + * Provides an opportunity to inspect repository state after installing a resource submitted to the + * {@link SlingSimulator}. + * + * @param lastPackage the last preinstall or scan package + * @param slingInstallable the sling installable + * @param inspectSession session providing access to repository state + * @throws RepositoryException because of access to a {@link Session} + * @since 2.1.0 + */ + default void appliedRepoInitScripts(PackageId lastPackage, SlingInstallable slingInstallable, Session inspectSession) + throws RepositoryException { + + } + + /** + * Provides an opportunity to inspect repository state after complete installation (including its content, + * subpackages, and Sling installable paths) of a package explicitly listed for scanning. This method is NOT called + * for any of its subpackages or embedded packages. + * + * @param packageId the scanned package id + * @param inspectSession session providing access to repository state + * @throws RepositoryException because of access to a {@link Session} + * @since 2.1.0 + */ + default void afterScanPackage(PackageId packageId, Session inspectSession) throws RepositoryException { + + } + } diff --git a/api/src/main/java/net/adamcin/oakpal/api/RepoInitScriptsInstallable.java b/api/src/main/java/net/adamcin/oakpal/api/RepoInitScriptsInstallable.java new file mode 100644 index 000000000..85e0978c1 --- /dev/null +++ b/api/src/main/java/net/adamcin/oakpal/api/RepoInitScriptsInstallable.java @@ -0,0 +1,64 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.api; + +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * Locates a jcr path that should be treated as an installable provider of repoinit scripts. + */ +public final class RepoInitScriptsInstallable implements SlingInstallable { + private final @NotNull PackageId parentId; + private final @NotNull String jcrPath; + private final @NotNull List scripts; + private final @Nullable OsgiConfigInstallable convertedFrom; + + public RepoInitScriptsInstallable(final @NotNull PackageId parentId, + final @NotNull String jcrPath, + final @NotNull List scripts, + final @Nullable OsgiConfigInstallable convertedFrom) { + this.parentId = parentId; + this.jcrPath = jcrPath; + this.scripts = scripts; + this.convertedFrom = convertedFrom; + } + + @Override + public @NotNull PackageId getParentId() { + return parentId; + } + + @Override + public @NotNull String getJcrPath() { + return jcrPath; + } + + @NotNull + public List getScripts() { + return scripts; + } + + @Nullable + @Override + public OsgiConfigInstallable getConvertedFrom() { + return convertedFrom; + } +} diff --git a/api/src/main/java/net/adamcin/oakpal/api/SilenceableCheck.java b/api/src/main/java/net/adamcin/oakpal/api/SilenceableCheck.java new file mode 100644 index 000000000..5902d61f9 --- /dev/null +++ b/api/src/main/java/net/adamcin/oakpal/api/SilenceableCheck.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.api; + +import org.osgi.annotation.versioning.ConsumerType; + +/** + * Extended interface marking progress checks that respect a mid-scan mode that expects oakpal to temporarily silence + * violations triggered by scan events. {@link ProgressCheck}s that do not implement this interface are simply blocked + * from receiving scan events for the duration of the silent period. + */ +@ConsumerType +public interface SilenceableCheck extends ProgressCheck { + + /** + * If silenced is true, this check must DISABLE the collection of violations until this method is called again + * with silenced=false. + * + * @param silenced true to silence violation reporting + */ + void setSilenced(boolean silenced); +} diff --git a/api/src/main/java/net/adamcin/oakpal/api/SlingInstallable.java b/api/src/main/java/net/adamcin/oakpal/api/SlingInstallable.java new file mode 100644 index 000000000..d0fd31b30 --- /dev/null +++ b/api/src/main/java/net/adamcin/oakpal/api/SlingInstallable.java @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.api; + +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; + +/** + * Type representing a Sling-installable resource JCR path. + */ +@ProviderType +public interface SlingInstallable { + + /** + * PackageId of the package that imported the resource. + * + * @return the parent package ID + */ + @NotNull PackageId getParentId(); + + /** + * The JCR path of the resource. + * + * @return the JCR path + */ + @NotNull String getJcrPath(); + + /** + * Get the installation hint if available. This would be the name of the parent node for osgi bundles. + * + * @return the osgi installation hint + */ + default @Nullable String getInstallationHint() { + return null; + } + + /** + * An installable might be converted from a generic form to a narrower form with special significance to Oakpal. + * This method provides a link to the original form, if any. + * + * @return the installable from which this was converted, or null + */ + default @Nullable SlingInstallable getConvertedFrom() { + return null; + } +} diff --git a/api/src/main/java/net/adamcin/oakpal/api/SlingSimulator.java b/api/src/main/java/net/adamcin/oakpal/api/SlingSimulator.java new file mode 100644 index 000000000..f237cb55e --- /dev/null +++ b/api/src/main/java/net/adamcin/oakpal/api/SlingSimulator.java @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.api; + +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; + +import javax.jcr.Node; + +/** + * Simulates aspects of the Sling runtime. + */ +@ProviderType +public interface SlingSimulator { + + /** + * Submit resource path for installation as an embedded FileVault package, generally located under {@code /apps/}. + * This should not be used to request traditional installation of {@code subpackages} under {@code /etc/packages}. + *

+ * Ideally, this method should be called by a check during + * {@link ProgressCheck#importedPath(PackageId, String, Node, PathAction)}. + * + * @param parentPackageId the parent package ID (not the embedded package ID) + * @param node the JCR node of the embedded JCR package + * @return a handle for the installable path or null + */ + @Nullable SlingInstallable prepareInstallableNode(@NotNull PackageId parentPackageId, + @NotNull Node node); + + /** + * Submit a resource path for installation as a list of raw repoinit scripts. The best real-world example at this + * time are + * May be called by a check during + * {@link ProgressCheck#importedPath(PackageId, String, Node, PathAction)}. + * + * @param installable the installable + * @return a handle for the installable path or null + */ + @Nullable SlingInstallable submitInstallable(@NotNull SlingInstallable installable); +} diff --git a/api/src/test/java/net/adamcin/oakpal/api/ProgressCheckTest.java b/api/src/test/java/net/adamcin/oakpal/api/ProgressCheckTest.java index cd200e94f..fab5a939e 100644 --- a/api/src/test/java/net/adamcin/oakpal/api/ProgressCheckTest.java +++ b/api/src/test/java/net/adamcin/oakpal/api/ProgressCheckTest.java @@ -16,11 +16,21 @@ package net.adamcin.oakpal.api; +import org.apache.jackrabbit.vault.packaging.PackageId; import org.junit.Assert; import org.junit.Test; +import javax.jcr.Node; import java.util.Collection; import java.util.Collections; +import java.util.concurrent.CompletableFuture; + +import static org.junit.Assert.assertSame; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; public class ProgressCheckTest { @@ -34,15 +44,47 @@ public Collection getReportedViolations() { }; Assert.assertNotNull("expect nonnull checkName", mock.getCheckName()); + mock.simulateSling(null, null); mock.startedScan(); mock.identifyPackage(null, null); - mock.identifySubpackage(null, null); mock.readManifest(null, null); mock.beforeExtract(null, null, null, null, null); mock.importedPath(null, null, null); mock.importedPath(null, null, null, null); mock.deletedPath(null, null, null); mock.afterExtract(null, null); + mock.identifySubpackage(null, null); + mock.beforeSlingInstall(null, null, null); + mock.identifyEmbeddedPackage(null, null, null); + mock.appliedRepoInitScripts(null, null, null); + mock.afterScanPackage(null, null); mock.finishedScan(); } + + @Test + public void testImportedPathDelegation() throws Exception { + final ProgressCheck check = mock(ProgressCheck.class); + doCallRealMethod().when(check).importedPath( + nullable(PackageId.class), nullable(String.class), nullable(Node.class), nullable(PathAction.class)); + + final CompletableFuture slot0 = new CompletableFuture<>(); + final CompletableFuture slot1 = new CompletableFuture<>(); + final CompletableFuture slot2 = new CompletableFuture<>(); + doAnswer(call -> { + slot0.complete(call.getArgument(0)); + slot1.complete(call.getArgument(1)); + slot2.complete(call.getArgument(2)); + return true; + }).when(check).importedPath(any(PackageId.class), any(String.class), any(Node.class)); + + final PackageId expectParam0 = PackageId.fromString("group:sub"); + final String expectParam1 = "/apps"; + final Node expectParam2 = mock(Node.class); + + check.importedPath(expectParam0, expectParam1, expectParam2, PathAction.NOOP); + + assertSame("expect param0", expectParam0, slot0.getNow(null)); + assertSame("expect param1", expectParam1, slot1.getNow(null)); + assertSame("expect param2", expectParam2, slot2.getNow(null)); + } } diff --git a/core/pom.xml b/core/pom.xml index 5422e1e06..134a528da 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -85,7 +85,9 @@ Oakpal-Checklist: OAKPAL-INF/checklists/basic.json Export-Package: net.adamcin.oakpal.core,\ net.adamcin.oakpal.core.checks,\ - net.adamcin.oakpal.core.opear + net.adamcin.oakpal.core.sling,\ + net.adamcin.oakpal.core.opear,\ + net.adamcin.oakpal.core.repoinit Private-Package: net.adamcin.oakpal.core.jcrfacade.* Import-Package: !aQute.*,\ !org.apache.sling.jcr.repoinit.*,\ @@ -131,6 +133,7 @@ biz.aQute.bnd:bndlib org.apache.sling:org.apache.sling.jcr.repoinit + org.apache.sling:org.apache.sling.installer.core @@ -151,6 +154,13 @@ org.apache.sling:org.apache.sling.jcr.repoinit org/apache/sling/jcr/repoinit/** + + + + org.apache.sling:org.apache.sling.installer.core + + org/apache/sling/installer/api/InstallableResource* + org/apache/sling/installer/core/impl/InternalResource* @@ -163,6 +173,14 @@ org.apache.sling.jcr.repoinit net.adamcin.oakpal.shaded.repoinit + + org.apache.felix + net.adamcin.oakpal.shaded.felix + + + org.apache.sling.installer + net.adamcin.oakpal.shaded.sling.installer + @@ -245,6 +263,12 @@ bndlib compile + + org.apache.sling + org.apache.sling.installer.core + 3.9.0 + compile + junit junit diff --git a/core/src/main/java/net/adamcin/oakpal/core/DefaultErrorListener.java b/core/src/main/java/net/adamcin/oakpal/core/DefaultErrorListener.java index a546e2446..fb2d5cbdf 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/DefaultErrorListener.java +++ b/core/src/main/java/net/adamcin/oakpal/core/DefaultErrorListener.java @@ -96,95 +96,95 @@ public Collection getReportedViolations() { } @Override - public void onNodeTypeRegistrationError(final Throwable e, final URL resource) { - if (e.getCause() != null) { - onNodeTypeRegistrationError(e.getCause(), resource); + public void onNodeTypeRegistrationError(final Throwable error, final URL resource) { + if (error.getCause() != null) { + onNodeTypeRegistrationError(error.getCause(), resource); } else { final String message = MessageFormat.format(getString("NodeType registration error ({0}): {1} \"{2}\""), - String.valueOf(resource), e.getClass().getName(), e.getMessage()); - LOGGER.trace("[onNodeTypeRegistrationError] stack trace for: " + message, e); + String.valueOf(resource), error.getClass().getName(), error.getMessage()); + LOGGER.trace("[onNodeTypeRegistrationError] stack trace for: " + message, error); reportViolation(new SimpleViolation(Severity.MAJOR, message)); } } @Override - public void onJcrNamespaceRegistrationError(final Throwable e, final String prefix, final String uri) { - if (e.getCause() != null) { - onJcrNamespaceRegistrationError(e.getCause(), prefix, uri); + public void onJcrNamespaceRegistrationError(final Throwable error, final String prefix, final String uri) { + if (error.getCause() != null) { + onJcrNamespaceRegistrationError(error.getCause(), prefix, uri); } else { final String message = MessageFormat.format(getString("JCR namespace registration error ({0}={1}): {2} \"{3}\""), - prefix, uri, e.getClass().getName(), e.getMessage()); - LOGGER.trace("[onJcrNamespaceRegistrationError] stack trace for: " + message, e); + prefix, uri, error.getClass().getName(), error.getMessage()); + LOGGER.trace("[onJcrNamespaceRegistrationError] stack trace for: " + message, error); reportViolation(new SimpleViolation(Severity.MAJOR, message)); } } @Override - public void onJcrPrivilegeRegistrationError(final Throwable e, final String jcrPrivilege) { - if (e.getCause() != null) { - onJcrPrivilegeRegistrationError(e.getCause(), jcrPrivilege); + public void onJcrPrivilegeRegistrationError(final Throwable error, final String jcrPrivilege) { + if (error.getCause() != null) { + onJcrPrivilegeRegistrationError(error.getCause(), jcrPrivilege); } else { final String message = MessageFormat.format(getString("JCR privilege registration error ({0}): {1} \"{2}\""), - jcrPrivilege, e.getClass().getName(), e.getMessage()); - LOGGER.trace("[onJcrPrivilegeRegistrationError] stack trace for: " + message, e); + jcrPrivilege, error.getClass().getName(), error.getMessage()); + LOGGER.trace("[onJcrPrivilegeRegistrationError] stack trace for: " + message, error); reportViolation(new SimpleViolation(Severity.MAJOR, message)); } } @Override - public void onForcedRootCreationError(final Throwable e, final ForcedRoot forcedRoot) { - if (e.getCause() != null) { - onForcedRootCreationError(e.getCause(), forcedRoot); + public void onForcedRootCreationError(final Throwable error, final ForcedRoot forcedRoot) { + if (error.getCause() != null) { + onForcedRootCreationError(error.getCause(), forcedRoot); } else { final String message = MessageFormat.format(getString("Forced root creation error ({0}): {1} \"{2}\""), - forcedRoot, e.getClass().getName(), e.getMessage()); - LOGGER.trace("[onForcedRootCreationError] stack trace for: " + message, e); + forcedRoot, error.getClass().getName(), error.getMessage()); + LOGGER.trace("[onForcedRootCreationError] stack trace for: " + message, error); reportViolation(new SimpleViolation(Severity.MAJOR, message)); } } @Override - public void onListenerException(final Exception e, final ProgressCheck listener, final PackageId packageId) { + public void onListenerException(final Exception error, final ProgressCheck listener, final PackageId packageId) { final String message = MessageFormat.format(getString("Listener error ({0}): {1} \"{2}\""), Optional.ofNullable(listener).map(lstr -> lstr.getClass().getName()).orElse(null), - e.getClass().getName(), e.getMessage()); - LOGGER.trace("[onListenerException] stack trace for: " + message, e); + error.getClass().getName(), error.getMessage()); + LOGGER.trace("[onListenerException] stack trace for: " + message, error); reportViolation(new SimpleViolation(Severity.MAJOR, message, packageId)); } @Override - public void onSubpackageException(final Exception e, final PackageId packageId) { + public void onSubpackageException(final Exception error, final PackageId packageId) { final String message = MessageFormat.format(getString("Package error: {0} \"{1}\""), - e.getClass().getName(), e.getMessage()); - LOGGER.trace("[onSubpackageException] stack trace for: " + message, e); + error.getClass().getName(), error.getMessage()); + LOGGER.trace("[onSubpackageException] stack trace for: " + message, error); reportViolation(new SimpleViolation(Severity.MAJOR, message, packageId)); } @Override - public void onImporterException(final Exception e, final PackageId packageId, final String path) { + public void onImporterException(final Exception error, final PackageId packageId, final String path) { // Ignore PathNotFoundException, as it is thrown A LOT - if (!(e instanceof PathNotFoundException)) { + if (!(error instanceof PathNotFoundException)) { final String message = MessageFormat.format(getString("{0} - Importer error: {1} \"{2}\""), - path, e.getClass().getName(), e.getMessage()); - LOGGER.trace("[onImporterException] stack trace for: " + message, e); + path, error.getClass().getName(), error.getMessage()); + LOGGER.trace("[onImporterException] stack trace for: " + message, error); reportViolation(new SimpleViolation(Severity.MAJOR, message, packageId)); } } @Override - public void onListenerPathException(final Exception e, final ProgressCheck handler, + public void onListenerPathException(final Exception error, final ProgressCheck handler, final PackageId packageId, final String path) { final String message = MessageFormat.format(getString("{0} - Listener error: {1} \"{2}\""), - path, e.getClass().getName(), e.getMessage()); - LOGGER.trace("[onListenerPathException] stack trace for: " + message, e); + path, error.getClass().getName(), error.getMessage()); + LOGGER.trace("[onListenerPathException] stack trace for: " + message, error); reportViolation(new SimpleViolation(Severity.MAJOR, message, packageId)); } @Override - public void onInstallHookError(final Throwable e, final PackageId packageId) { + public void onInstallHookError(final Throwable error, final PackageId packageId) { final String message = MessageFormat.format(getString("InstallHook error: {0} \"{1}\""), - Optional.ofNullable(e.getCause()).orElse(e).getClass().getName(), e.getMessage()); - LOGGER.trace("[onInstallHookError] stack trace for: " + message, e); + Optional.ofNullable(error.getCause()).orElse(error).getClass().getName(), error.getMessage()); + LOGGER.trace("[onInstallHookError] stack trace for: " + message, error); reportViolation(new SimpleViolation(Severity.MAJOR, message, packageId)); } @@ -196,21 +196,21 @@ public void onProhibitedInstallHookRegistration(final PackageId packageId) { } @Override - public void onRepoInitUrlError(final Throwable e, final URL repoinitUrl) { + public void onRepoInitUrlError(final Throwable error, final URL repoinitUrl) { final String message = MessageFormat.format(getString("repoinit url error ({0}): {1} \"{2}\""), - String.valueOf(repoinitUrl), e.getClass().getName(), e.getMessage()); - LOGGER.trace("[onRepoInitUrlError] stack trace for: " + message, e); + String.valueOf(repoinitUrl), error.getClass().getName(), error.getMessage()); + LOGGER.trace("[onRepoInitUrlError] stack trace for: " + message, error); reportViolation(new SimpleViolation(Severity.MAJOR, message)); } @Override - public void onRepoInitInlineError(final Throwable e, final List repoinits) { + public void onRepoInitInlineError(final Throwable error, final List repoinits) { final String firstLine = repoinits == null || repoinits.isEmpty() ? "" : repoinits.get(0).split("\\r?\\n")[0] + "..."; final String message = MessageFormat.format(getString("repoinit inline error ({0}): {1} \"{2}\""), - firstLine, e.getClass().getName(), e.getMessage()); - LOGGER.trace("[onRepoInitInlineError] stack trace for: " + message, e); + firstLine, error.getClass().getName(), error.getMessage()); + LOGGER.trace("[onRepoInitInlineError] stack trace for: " + message, error); reportViolation(new SimpleViolation(Severity.MAJOR, message)); } } diff --git a/core/src/main/java/net/adamcin/oakpal/core/ErrorListener.java b/core/src/main/java/net/adamcin/oakpal/core/ErrorListener.java index f4363b6a3..8199062cc 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/ErrorListener.java +++ b/core/src/main/java/net/adamcin/oakpal/core/ErrorListener.java @@ -19,6 +19,8 @@ import net.adamcin.oakpal.api.ProgressCheck; import net.adamcin.oakpal.api.ScanListener; import net.adamcin.oakpal.api.ViolationReporter; +import net.adamcin.oakpal.api.EmbeddedPackageInstallable; +import net.adamcin.oakpal.api.RepoInitScriptsInstallable; import org.apache.jackrabbit.vault.packaging.PackageId; import org.osgi.annotation.versioning.ConsumerType; @@ -34,91 +36,91 @@ public interface ErrorListener extends ScanListener, ViolationReporter { /** * Called for each unresolved error thrown during node type definition auto-installation. * - * @param e the error. + * @param error the error. * @param resource the classpath resource of the failed Sling-Nodetypes entry. */ - default void onNodeTypeRegistrationError(final Throwable e, final URL resource) { + default void onNodeTypeRegistrationError(final Throwable error, final URL resource) { } /** * Called for each unresolved error thrown during JCR namespace prefix registration. * - * @param e the error. + * @param error the error. * @param prefix the prefix being registered. * @param uri the uri being registered. */ - default void onJcrNamespaceRegistrationError(final Throwable e, final String prefix, final String uri) { + default void onJcrNamespaceRegistrationError(final Throwable error, final String prefix, final String uri) { } /** * Called for each unresolved error thrown during JCR privilege registration. * - * @param e the error. + * @param error the error. * @param jcrPrivilege the jcrPrivilege being registered. */ - default void onJcrPrivilegeRegistrationError(final Throwable e, final String jcrPrivilege) { + default void onJcrPrivilegeRegistrationError(final Throwable error, final String jcrPrivilege) { } /** * Called for each error thrown during creation of a forced JCR root. * - * @param e the error. + * @param error the error. * @param forcedRoot the root path being created. */ - default void onForcedRootCreationError(final Throwable e, final ForcedRoot forcedRoot) { + default void onForcedRootCreationError(final Throwable error, final ForcedRoot forcedRoot) { } /** * Called when a {@link ProgressCheck} throws an exception. * - * @param e the error + * @param error the error * @param listener the listener * @param packageId the current package id */ - default void onListenerException(final Exception e, final ProgressCheck listener, final PackageId packageId) { + default void onListenerException(final Exception error, final ProgressCheck listener, final PackageId packageId) { } /** * Called when a {@link ProgressCheck} throws an exception when handling an imported path. * - * @param e the error + * @param error the error * @param handler the handler * @param packageId the current package id * @param path the current path */ - default void onListenerPathException(final Exception e, final ProgressCheck handler, final PackageId packageId, final String path) { + default void onListenerPathException(final Exception error, final ProgressCheck handler, final PackageId packageId, final String path) { } /** * Called when the package FileVault importer encounters an error such as an XML syntax exception. * - * @param e the caught exception + * @param error the caught exception * @param packageId the current package ID * @param path the related repository path, if applicable */ - default void onImporterException(final Exception e, final PackageId packageId, final String path) { + default void onImporterException(final Exception error, final PackageId packageId, final String path) { } /** * Called when an exception was thrown when attempting to open or extract a package. * - * @param e the Exception that was thrown + * @param error the Exception that was thrown * @param packageId the offending package id */ - default void onSubpackageException(final Exception e, final PackageId packageId) { + default void onSubpackageException(final Exception error, final PackageId packageId) { } /** * Called when an exception is thrown when attempting to register install hooks for a particular package. * - * @param e the error thrown + * @param error the error thrown * @param packageId the package attempting to register install hooks */ - default void onInstallHookError(final Throwable e, final PackageId packageId) { + default void onInstallHookError(final Throwable error, final PackageId packageId) { } @@ -136,10 +138,10 @@ default void onProhibitedInstallHookRegistration(final PackageId packageId) { * Called for an IOException or RepoInitParsingException when parsing a repoinit url during * {@code InitStage.initSession()}. * - * @param e the error thrown + * @param error the error thrown * @param repoinitUrl the repoinit url */ - default void onRepoInitUrlError(final Throwable e, final URL repoinitUrl) { + default void onRepoInitUrlError(final Throwable error, final URL repoinitUrl) { } @@ -147,10 +149,35 @@ default void onRepoInitUrlError(final Throwable e, final URL repoinitUrl) { * Called for an IOException or RepoInitParsingException when parsing a list of repoinit scripts during * {@code InitStage.initSession()}. * - * @param e the error thrown + * @param error the error thrown * @param repoinits the repoinit scripts */ - default void onRepoInitInlineError(final Throwable e, final List repoinits) { + default void onRepoInitInlineError(final Throwable error, final List repoinits) { + + } + + /** + * Called for an IOException or RepoInitParsingException when parsing an installable repoinit script submitted + * to a {@link net.adamcin.oakpal.api.SlingSimulator}. + * + * @param error the error thrown + * @param failedScript the script that failed + * @param installable the repoinit scripts installable + */ + default void onSlingRepoInitScriptsError(final Throwable error, + final String failedScript, + final RepoInitScriptsInstallable installable) { + } + + /** + * Called for an IOException, PackageException, or RepositoryException when installing an embedded package submitted + * to a {@link net.adamcin.oakpal.api.SlingSimulator}. + * + * @param error the error thrown + * @param installable the subpackage installable + */ + default void onSlingEmbeddedPackageError(final Throwable error, + final EmbeddedPackageInstallable installable) { } } 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 d4f0cfe5f..fdd1bd925 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/OakMachine.java +++ b/core/src/main/java/net/adamcin/oakpal/core/OakMachine.java @@ -19,6 +19,12 @@ import net.adamcin.oakpal.api.Fun; import net.adamcin.oakpal.api.PathAction; import net.adamcin.oakpal.api.ProgressCheck; +import net.adamcin.oakpal.api.SilenceableCheck; +import net.adamcin.oakpal.api.SlingInstallable; +import net.adamcin.oakpal.core.sling.DefaultSlingSimulator; +import net.adamcin.oakpal.api.EmbeddedPackageInstallable; +import net.adamcin.oakpal.api.RepoInitScriptsInstallable; +import net.adamcin.oakpal.core.sling.SlingSimulatorBackend; import org.apache.jackrabbit.JcrConstants; import org.apache.jackrabbit.api.JackrabbitRepository; import org.apache.jackrabbit.commons.cnd.DefinitionBuilderFactory; @@ -67,15 +73,20 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import java.io.StringReader; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Properties; +import java.util.Set; +import java.util.function.BiConsumer; import java.util.function.BiPredicate; +import java.util.function.Consumer; import java.util.function.Supplier; import java.util.jar.JarInputStream; import java.util.jar.Manifest; @@ -120,6 +131,10 @@ public final class OakMachine { private final RepoInitProcessor repoInitProcessor; + private final SlingSimulatorBackend slingSimulator; + + private final Set runModes; + private OakMachine(final Packaging packagingService, final List progressChecks, final ErrorListener errorListener, @@ -132,7 +147,9 @@ private OakMachine(final Packaging packagingService, final InstallHookPolicy scanInstallHookPolicy, final Supplier nodeStoreSupplier, final SubpackageSilencer subpackageSilencer, - final RepoInitProcessor repoInitProcessor) { + final RepoInitProcessor repoInitProcessor, + final SlingSimulatorBackend slingSimulator, + final Set runModes) { this.packagingService = packagingService != null ? packagingService : newOakpalPackagingService(); this.progressChecks = progressChecks; this.errorListener = errorListener; @@ -148,6 +165,10 @@ private OakMachine(final Packaging packagingService, this.repoInitProcessor = repoInitProcessor != null ? repoInitProcessor : newDefaultRepoInitProcessor(Util.getDefaultClassLoader()); + this.slingSimulator = slingSimulator != null ? slingSimulator : DefaultSlingSimulator.instance(); + this.runModes = runModes != null + ? Collections.unmodifiableSet(new LinkedHashSet<>(runModes)) + : Collections.emptySet(); } /** @@ -187,6 +208,12 @@ public static class Builder { private SubpackageSilencer subpackageSilencer; + private RepoInitProcessor repoInitProcesser; + + private SlingSimulatorBackend slingSimulator; + + private Set runModes; + /** * Provide a {@link Packaging} service for use in retrieving a {@link JcrPackageManager} for an admin session. *

@@ -430,6 +457,43 @@ public Builder withSubpackageSilencer(final SubpackageSilencer subpackageSilence return this; } + /** + * Provide a RepoInitProcessor. + * + * @param repoInitProcesser the repoinit processor + * @return my builder self + * @since 2.1.0 + */ + public Builder withRepoInitProcesser(final RepoInitProcessor repoInitProcesser) { + this.repoInitProcesser = repoInitProcesser; + return this; + } + + /** + * Provide a sling simulator + * + * @param slingSimulator the sling simulator backend + * @return my builder self + * @since 2.1.0 + */ + public Builder withSlingSimulator(final SlingSimulatorBackend slingSimulator) { + this.slingSimulator = slingSimulator; + return this; + } + + + /** + * Provide a set of simulated sling run modes. + * + * @param runModes the set of sling run modes + * @return my builder self + * @since 2.1.0 + */ + public Builder withRunModes(final Set runModes) { + this.runModes = runModes; + return this; + } + /** * Construct a {@link OakMachine} from the {@link Builder} state. * @@ -448,7 +512,9 @@ public OakMachine build() { scanInstallHookPolicy, nodeStoreSupplier, subpackageSilencer, - null); + repoInitProcesser, + slingSimulator, + runModes); } } @@ -541,6 +607,8 @@ public void adminInitAndInspect(final InspectBody inspe initStage.initSession(admin, getErrorListener(), repoInitProcessor); } + initSlingSimulator(admin, manager, errorListener); + for (final URL url : preInstallUrls) { processPackageUrl(admin, manager, true, url); } @@ -555,6 +623,12 @@ public void adminInitAndInspect(final InspectBody inspe } } + void initSlingSimulator(final Session admin, final JcrPackageManager manager, final ErrorListener errorListener) { + slingSimulator.setSession(admin); + slingSimulator.setPackageManager(manager); + slingSimulator.setErrorListener(getErrorListener()); + } + /** * Perform a scan of the provided package file or files. * @@ -606,12 +680,15 @@ public List scanPackages(final List files) throws AbortedScan initStage.initSession(admin, getErrorListener(), repoInitProcessor); } + initSlingSimulator(admin, manager, errorListener); + progressChecks.forEach(check -> check.simulateSling(slingSimulator, runModes)); + slingSimulator.startedScan(); + progressChecks.forEach(ProgressCheck::startedScan); + for (final URL url : preInstallUrls) { processPackageUrl(admin, manager, true, url); } - progressChecks.forEach(ProgressCheck::startedScan); - if (files != null) { for (final File file : files) { processPackageFile(admin, manager, false, file); @@ -622,6 +699,7 @@ public List scanPackages(final List files) throws AbortedScan throw new AbortedScanException(e); } finally { progressChecks.forEach(ProgressCheck::finishedScan); + slingSimulator.finishedScan(); if (admin != null) { admin.logout(); @@ -634,7 +712,6 @@ public List scanPackages(final List files) throws AbortedScan List reports = new ArrayList<>(); reports.add(SimpleReport.generateReport(getErrorListener())); - List listenerReports = progressChecks.stream() .map(SimpleReport::generateReport) .collect(Collectors.toList()); @@ -690,24 +767,21 @@ private void processPackage(Session admin, JcrPackageManager manager, JcrPackage throws IOException, PackageException, RepositoryException { final PackageId packageId = jcrPackage.getPackage().getId(); - - if (!preInstall) { - Optional.ofNullable(jcrPackage.getData()).map(uncheck1(Property::getBinary)).ifPresent( - uncheckVoid1(binary -> { - try (InputStream input = binary.getStream(); - JarInputStream jarInput = new JarInputStream(input)) { - final Manifest manifest = jarInput.getManifest(); - if (manifest != null) { - progressChecks.forEach(handler -> - handler.readManifest(packageId, new Manifest(manifest))); - } + Optional.ofNullable(jcrPackage.getData()).map(uncheck1(Property::getBinary)).ifPresent( + uncheckVoid1(binary -> { + try (InputStream input = binary.getStream(); + JarInputStream jarInput = new JarInputStream(input)) { + final Manifest manifest = jarInput.getManifest(); + if (manifest != null) { + propagateCheckPackageEvent(preInstall, packageId, + handler -> handler.readManifest(packageId, new Manifest(manifest))); } - })); - } + } + })); final Session inspectSession = Util.wrapSessionReadOnly(admin); final ProgressTrackerListener tracker = - new ImporterListenerAdapter(packageId, progressChecks, inspectSession, preInstall); + new ImporterListenerAdapter(packageId, inspectSession, preInstall); InternalImportOptions options = new InternalImportOptions(packageId, Packaging.class.getClassLoader()); options.setNonRecursive(true); @@ -734,16 +808,9 @@ private void processPackage(Session admin, JcrPackageManager manager, JcrPackage throw new PackageException("Package is not valid: " + packageId); } - if (!preInstall) { - progressChecks.forEach(handler -> { - try { - handler.beforeExtract(packageId, inspectSession, - vaultPackage.getProperties(), vaultPackage.getMetaInf(), subpacks); - } catch (final Exception e) { - getErrorListener().onListenerException(e, handler, packageId); - } - }); - } + propagateCheckPackageEvent(preInstall, packageId, handler -> + handler.beforeExtract(packageId, inspectSession, vaultPackage.getProperties(), + vaultPackage.getMetaInf(), subpacks)); jcrPackage.extract(options); admin.save(); @@ -761,15 +828,7 @@ private void processPackage(Session admin, JcrPackageManager manager, JcrPackage jcrPackage.close(); - if (!preInstall) { - progressChecks.forEach(handler -> { - try { - handler.afterExtract(packageId, inspectSession); - } catch (final Exception e) { - getErrorListener().onListenerException(e, handler, packageId); - } - }); - } + propagateCheckPackageEvent(preInstall, packageId, handler -> handler.afterExtract(packageId, inspectSession)); for (PackageId subpackId : installableSubpacks) { processSubpackage(admin, manager, subpackId, packageId, @@ -777,48 +836,144 @@ private void processPackage(Session admin, JcrPackageManager manager, JcrPackage } } - final void processSubpackage(Session admin, JcrPackageManager manager, - PackageId packageId, PackageId parentId, final boolean preInstall) - throws RepositoryException { - try (JcrPackage jcrPackage = manager.open(packageId)) { - - if (!preInstall) { - progressChecks.forEach(handler -> { - try { - handler.identifySubpackage(packageId, parentId); - } catch (final Exception e) { - getErrorListener().onListenerException(e, handler, packageId); - } - }); + static Consumer + newProgressCheckEventConsumer(final boolean silenced, + final @NotNull Fun.ThrowingConsumer checkVisitor, + final @NotNull BiConsumer onError) { + Consumer consumer = check -> { + try { + if (silenced && check instanceof SilenceableCheck) { + ((SilenceableCheck) check).setSilenced(silenced); + } + if (!silenced || check instanceof SilenceableCheck) { + checkVisitor.tryAccept(check); + } + } catch (final Exception e) { + if (!silenced) { + onError.accept(check, e); + } + } + }; + return consumer.andThen(check -> { + if (silenced && check instanceof SilenceableCheck) { + ((SilenceableCheck) check).setSilenced(false); } + }); + } - processPackage(admin, manager, jcrPackage, preInstall); + final void propagateCheckPackageEvent(final boolean silenced, + final @NotNull PackageId packageId, + final @NotNull Fun.ThrowingConsumer checkVisitor) { + final Consumer checkConsumer = newProgressCheckEventConsumer(silenced, checkVisitor, + (check, error) -> getErrorListener().onListenerException(error, check, packageId)); + progressChecks.forEach(checkConsumer); + } + + final void propagateCheckPathEvent(final boolean silenced, + final @NotNull PackageId packageId, + final @NotNull String path, + final @NotNull Fun.ThrowingConsumer checkVisitor) { + final Consumer checkConsumer = newProgressCheckEventConsumer(silenced, checkVisitor, + (check, error) -> getErrorListener().onListenerPathException(error, check, packageId, path)); + progressChecks.forEach(checkConsumer); + } + final void internalProcessSubpackage(final @NotNull Session admin, + final @NotNull JcrPackageManager manager, + final @NotNull PackageId packageId, + final boolean preInstall, + final @NotNull Fun.ThrowingSupplier jcrPackageSupplier, + final @NotNull Fun.ThrowingConsumer identifyEvent, + final @NotNull Consumer onError) throws RepositoryException { + try (JcrPackage jcrPackage = jcrPackageSupplier.tryGet()) { + if (jcrPackage != null) { + propagateCheckPackageEvent(preInstall, packageId, identifyEvent); + + processPackage(admin, manager, jcrPackage, preInstall); + } else { + throw new PackageException("JcrPackageManager returned null package"); + } } catch (IOException | PackageException | RepositoryException e) { - getErrorListener().onSubpackageException(e, packageId); + onError.accept(e); admin.refresh(false); + } catch (Exception e) { + onError.accept(e); + } + } + + final void processSubpackage(final @NotNull Session admin, + final @NotNull JcrPackageManager manager, + final @NotNull PackageId packageId, + final @NotNull PackageId parentId, + final boolean preInstall) throws RepositoryException { + internalProcessSubpackage(admin, manager, packageId, preInstall, + () -> manager.open(packageId), + check -> check.identifySubpackage(packageId, parentId), + error -> getErrorListener().onSubpackageException(error, packageId)); + } + + final void processEmbeddedPackage(final @NotNull Session admin, + final @NotNull JcrPackageManager manager, + final @NotNull EmbeddedPackageInstallable installable, + final boolean preInstall) throws RepositoryException { + final Consumer onError = + error -> getErrorListener().onSlingEmbeddedPackageError(error, installable); + Fun.ThrowingSupplier supplier = slingSimulator.openEmbeddedPackage(installable); + if (supplier != null) { + internalProcessSubpackage(admin, manager, installable.getEmbeddedId(), preInstall, supplier, + check -> check.identifyEmbeddedPackage( + installable.getEmbeddedId(), + installable.getParentId(), + installable.getJcrPath()), + onError); } } - private void processUploadedPackage(final Session admin, - final JcrPackageManager manager, + private void processUploadedPackage(final @NotNull Session admin, + final @NotNull JcrPackageManager manager, final boolean preInstall, - final JcrPackage jcrPackage) throws IOException, PackageException, RepositoryException { + final @NotNull JcrPackage jcrPackage) + throws IOException, PackageException, RepositoryException { final VaultPackage vaultPackage = jcrPackage.getPackage(); final PackageId packageId = vaultPackage.getId(); final File packageFile = vaultPackage.getFile(); + propagateCheckPackageEvent(preInstall, packageId, + handler -> handler.identifyPackage(packageId, packageFile)); + processPackage(admin, manager, jcrPackage, preInstall); + processInstallableQueue(admin, manager, packageId, preInstall); + propagateCheckPackageEvent(preInstall, packageId, + handler -> handler.afterScanPackage(packageId, Util.wrapSessionReadOnly(admin))); - if (!preInstall) { - progressChecks.forEach(handler -> { - try { - handler.identifyPackage(packageId, packageFile); - } catch (Exception e) { - getErrorListener().onListenerException(e, handler, packageId); + } + + void processInstallableQueue(final @NotNull Session admin, + final @NotNull JcrPackageManager manager, + final @NotNull PackageId lastPackageId, + final boolean preInstall) throws RepositoryException { + final Session inspectSession = Util.wrapSessionReadOnly(admin); + SlingInstallable dequeued = slingSimulator.dequeueInstallable(); + while (dequeued != null) { + final SlingInstallable installable = dequeued; + propagateCheckPackageEvent(preInstall, installable.getParentId(), + check -> check.beforeSlingInstall(lastPackageId, installable, inspectSession)); + if (installable instanceof RepoInitScriptsInstallable) { + for (final String repoInitScript : + slingSimulator.openRepoInitScripts((RepoInitScriptsInstallable) installable)) { + try (Reader reader = new StringReader(repoInitScript)) { + repoInitProcessor.apply(admin, reader); + } catch (final Exception e) { + getErrorListener().onSlingRepoInitScriptsError(e, repoInitScript, + (RepoInitScriptsInstallable) installable); + } } - }); + } else if (installable instanceof EmbeddedPackageInstallable) { + processEmbeddedPackage(admin, manager, (EmbeddedPackageInstallable) installable, preInstall); + } + propagateCheckPackageEvent(preInstall, installable.getParentId(), + check -> check.appliedRepoInitScripts(lastPackageId, installable, inspectSession)); + // do this at the end of the while scope, obviously. + dequeued = slingSimulator.dequeueInstallable(); } - - processPackage(admin, manager, jcrPackage, preInstall); } final void processPackageUrl(final @NotNull Session admin, @@ -942,56 +1097,46 @@ private void installVltNodetypes(final Session admin) throws RepositoryException final class ImporterListenerAdapter implements ProgressTrackerListener { private final PackageId packageId; - private final List handlers; - private final Session session; - private final boolean preInstall; + private final boolean silenced; - ImporterListenerAdapter(PackageId packageId, List handlers, Session session, boolean preInstall) { + ImporterListenerAdapter(PackageId packageId, Session session, boolean silenced) { this.packageId = packageId; - this.handlers = handlers; this.session = session; - this.preInstall = preInstall; + this.silenced = silenced; } @Override public void onMessage(Mode mode, String action, String path) { - if (preInstall) { - return; - } // NOP("-"), MOD("U"), REP("R"), ERR("E"), ADD("A"), DEL("D"), MIS("!") if (path != null && path.startsWith("/")) { if ("D".equals(action)) { // deleted - handlers.forEach(handler -> { - try { - handler.deletedPath(packageId, path, session); - } catch (final Exception e) { - OakMachine.this.getErrorListener().onListenerPathException(e, handler, packageId, path); - } - }); + propagateCheckPathEvent(silenced, packageId, path, + check -> check.deletedPath(packageId, path, session)); } else if ("ARU-".contains(action)) { // added, replaced, updated try { Node node = session.getNode(path); - handlers.forEach(handler -> { - try { - handler.importedPath(packageId, path, node, PathAction.fromShortCode(action)); - } catch (final Exception e) { - OakMachine.this.getErrorListener().onListenerPathException(e, handler, packageId, path); - } - }); + propagateCheckPathEvent(silenced, packageId, path, check -> + check.importedPath(packageId, path, node, PathAction.fromShortCode(action))); } catch (RepositoryException e) { - OakMachine.this.getErrorListener().onImporterException(e, packageId, path); + if (!silenced) { + getErrorListener().onImporterException(e, packageId, path); + } } } else if ("E".equals(action)) { - onError(mode, path, new RuntimeException("Unknown error")); + if (!silenced) { + onError(mode, path, new RuntimeException("Unknown error")); + } } } } @Override public void onError(Mode mode, String path, Exception e) { - OakMachine.this.getErrorListener().onImporterException(e, packageId, path); + if (!silenced) { + getErrorListener().onImporterException(e, packageId, path); + } } } diff --git a/core/src/main/java/net/adamcin/oakpal/core/OakpalPlan.java b/core/src/main/java/net/adamcin/oakpal/core/OakpalPlan.java index 3aa7005cd..8468e6a00 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/OakpalPlan.java +++ b/core/src/main/java/net/adamcin/oakpal/core/OakpalPlan.java @@ -25,6 +25,7 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Optional; @@ -47,6 +48,8 @@ public final class OakpalPlan implements JsonObjectConvertible { @ProviderType public interface JsonKeys { + String runModes(); + String checklists(); String checks(); @@ -71,6 +74,11 @@ public interface JsonKeys { } private static final JsonKeys KEYS = new JsonKeys() { + @Override + public String runModes() { + return "runModes"; + } + @Override public String checklists() { return "checklists"; @@ -155,6 +163,7 @@ public static JsonKeys keys() { private final URL base; private final String name; private final JsonObject originalJson; + private final List runModes; private final List checklists; private final List preInstallUrls; private final List jcrNamespaces; @@ -170,6 +179,7 @@ public static JsonKeys keys() { private OakpalPlan(final @Nullable URL base, final @Nullable JsonObject originalJson, final @NotNull String name, + final @NotNull List runModes, final @NotNull List checklists, final @NotNull List preInstallUrls, final @NotNull List jcrNamespaces, @@ -184,6 +194,7 @@ private OakpalPlan(final @Nullable URL base, this.base = base; this.originalJson = originalJson; this.name = name; + this.runModes = runModes; this.checklists = checklists; this.preInstallUrls = preInstallUrls; this.jcrNamespaces = jcrNamespaces; @@ -209,6 +220,10 @@ public JsonObject getOriginalJson() { return originalJson; } + public List getRunModes() { + return runModes; + } + public List getChecklists() { return checklists; } @@ -301,6 +316,7 @@ public JsonObject toJson() { final NamespaceMapping mapping = JsonCnd.toNamespaceMapping(jcrNamespaces); return JavaxJson.obj() .key(keys().preInstallUrls()).opt(preInstallStrings) + .key(keys().runModes()).opt(runModes) .key(keys().checklists()).opt(checklists) .key(keys().checks()).opt(checks) .key(keys().repoInitUrls()).opt(repoInitUrlStrings) @@ -370,6 +386,7 @@ public OakMachine.Builder toOakMachineBuilder(final @Nullable ErrorListener erro .withPreInstallUrls(preInstallUrls) .withInstallHookPolicy(installHookPolicy) .withInstallHookClassLoader(classLoader) + .withRunModes(new HashSet<>(getRunModes())) .withEnablePreInstallHooks(enablePreInstallHooks); } @@ -463,6 +480,7 @@ public static final class Builder { private InstallHookPolicy scanInstallHookPolicy; private List repoInitUrls = Collections.emptyList(); private List repoInits = Collections.emptyList(); + private List runModes = Collections.emptyList(); public Builder(final @Nullable URL base, final @Nullable String name) { this.base = base; @@ -542,8 +560,13 @@ public Builder withInstallHookPolicy(final InstallHookPolicy scanInstallHookPoli return this; } + public Builder withRunModes(final @NotNull List runModes) { + this.runModes = new ArrayList<>(runModes); + return this; + } + private OakpalPlan build(final @Nullable JsonObject originalJson) { - return new OakpalPlan(base, originalJson, name, checklists, preInstallUrls, jcrNamespaces, + return new OakpalPlan(base, originalJson, name, runModes, checklists, preInstallUrls, jcrNamespaces, jcrNodetypes, jcrPrivileges, forcedRoots, checks, enablePreInstallHooks, scanInstallHookPolicy, repoInitUrls, repoInits); } diff --git a/core/src/main/java/net/adamcin/oakpal/core/ProgressCheckAliasFacade.java b/core/src/main/java/net/adamcin/oakpal/core/ProgressCheckAliasFacade.java index 2296f173f..c9d16a0f7 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/ProgressCheckAliasFacade.java +++ b/core/src/main/java/net/adamcin/oakpal/core/ProgressCheckAliasFacade.java @@ -19,6 +19,9 @@ import net.adamcin.oakpal.api.PathAction; import net.adamcin.oakpal.api.ProgressCheck; import net.adamcin.oakpal.api.ProgressCheckFactory; +import net.adamcin.oakpal.api.SilenceableCheck; +import net.adamcin.oakpal.api.SlingInstallable; +import net.adamcin.oakpal.api.SlingSimulator; import net.adamcin.oakpal.api.Violation; import org.apache.jackrabbit.vault.fs.config.MetaInf; import org.apache.jackrabbit.vault.packaging.PackageId; @@ -33,6 +36,7 @@ import java.util.Collection; import java.util.List; import java.util.ResourceBundle; +import java.util.Set; import java.util.jar.Manifest; /** @@ -40,14 +44,18 @@ * 1) ensure that a configured checkName is actually respected * 2) guard {@link ProgressCheckFactory}s from being externally re-configured during a scan */ -class ProgressCheckAliasFacade implements ProgressCheck { +class ProgressCheckAliasFacade implements SilenceableCheck { - private final ProgressCheck wrapped; + private final SilenceableCheck wrapped; private final String alias; ProgressCheckAliasFacade(final @NotNull ProgressCheck wrapped, final @Nullable String alias) { - this.wrapped = wrapped; + if (wrapped instanceof SilenceableCheck) { + this.wrapped = (SilenceableCheck) wrapped; + } else { + this.wrapped = new SilencingCheckFacade(wrapped); + } this.alias = alias; } @@ -70,14 +78,24 @@ public void setResourceBundle(final ResourceBundle resourceBundle) { wrapped.setResourceBundle(resourceBundle); } + @Override + public Collection getReportedViolations() { + return wrapped.getReportedViolations(); + } + @Override public void startedScan() { wrapped.startedScan(); } @Override - public Collection getReportedViolations() { - return wrapped.getReportedViolations(); + public void setSilenced(final boolean silenced) { + wrapped.setSilenced(silenced); + } + + @Override + public void simulateSling(final SlingSimulator slingSimulator, final Set runModes) { + wrapped.simulateSling(slingSimulator, runModes); } @Override @@ -90,11 +108,6 @@ public void readManifest(final PackageId packageId, final Manifest manifest) { wrapped.readManifest(packageId, manifest); } - @Override - public void identifySubpackage(final PackageId packageId, final PackageId parentId) { - wrapped.identifySubpackage(packageId, parentId); - } - @Override public void beforeExtract(final PackageId packageId, final Session inspectSession, final PackageProperties packageProperties, final MetaInf metaInf, @@ -119,6 +132,35 @@ public void afterExtract(final PackageId packageId, final Session inspectSession wrapped.afterExtract(packageId, inspectSession); } + @Override + public void identifySubpackage(final PackageId packageId, final PackageId parentId) { + wrapped.identifySubpackage(packageId, parentId); + } + + @Override + public void beforeSlingInstall(final PackageId lastPackage, + final SlingInstallable slingInstallable, + final Session inspectSession) throws RepositoryException { + wrapped.beforeSlingInstall(lastPackage, slingInstallable, inspectSession); + } + + @Override + public void identifyEmbeddedPackage(final PackageId packageId, final PackageId parentId, final String jcrPath) { + wrapped.identifyEmbeddedPackage(packageId, parentId, jcrPath); + } + + @Override + public void appliedRepoInitScripts(final PackageId lastPackage, + final SlingInstallable slingInstallable, + final Session inspectSession) throws RepositoryException { + wrapped.appliedRepoInitScripts(lastPackage, slingInstallable, inspectSession); + } + + @Override + public void afterScanPackage(final PackageId packageId, final Session inspectSession) throws RepositoryException { + wrapped.afterScanPackage(packageId, inspectSession); + } + @Override public void finishedScan() { wrapped.finishedScan(); diff --git a/core/src/main/java/net/adamcin/oakpal/core/ScriptProgressCheck.java b/core/src/main/java/net/adamcin/oakpal/core/ScriptProgressCheck.java index 62150a7c5..8a31560af 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/ScriptProgressCheck.java +++ b/core/src/main/java/net/adamcin/oakpal/core/ScriptProgressCheck.java @@ -24,6 +24,8 @@ import net.adamcin.oakpal.api.Result; import net.adamcin.oakpal.api.Severity; import net.adamcin.oakpal.api.SimpleViolation; +import net.adamcin.oakpal.api.SlingInstallable; +import net.adamcin.oakpal.api.SlingSimulator; import net.adamcin.oakpal.api.Violation; import org.apache.jackrabbit.vault.fs.config.MetaInf; import org.apache.jackrabbit.vault.packaging.PackageId; @@ -66,12 +68,12 @@ *

*
getCheckName()
*
{@link ProgressCheck#getCheckName()}
+ *
simulateSling(slingSimulator, runModes)
+ *
{@link ProgressCheck#simulateSling(SlingSimulator, Set)} ()}
*
startedScan()
*
{@link ProgressCheck#startedScan()}
*
identifyPackage(packageId, packageFile)
*
{@link ProgressCheck#identifyPackage(PackageId, File)}
- *
identifySubpackage(packageId, parentPackageId)
- *
{@link ProgressCheck#identifySubpackage(PackageId, PackageId)}
*
beforeExtract(packageId, inspectSession, packageProperties, metaInf, subpackageIds)
*
{@link ProgressCheck#beforeExtract(PackageId, Session, PackageProperties, MetaInf, List)}
*
importedPath(packageId, path, node, action)
@@ -80,6 +82,16 @@ *
{@link ProgressCheck#deletedPath(PackageId, String, Session)}
*
afterExtract(packageId, inspectSession)
*
{@link ProgressCheck#afterExtract(PackageId, Session)}
+ *
identifySubpackage(packageId, parentPackageId)
+ *
{@link ProgressCheck#identifySubpackage(PackageId, PackageId)}
+ *
beforeSlingInstall(packageId, slingInstallable, inspectSession)
+ *
{@link ProgressCheck#beforeSlingInstall(PackageId, SlingInstallable, Session)}
+ *
identifyEmbeddedPackage(packageId, parentPackageId, jcrPath)
+ *
{@link ProgressCheck#identifyEmbeddedPackage(PackageId, PackageId, String)}
+ *
afterSlingInstall(packageId, slingInstallable, inspectSession)
+ *
{@link ProgressCheck#appliedRepoInitScripts(PackageId, SlingInstallable, Session)}
+ *
afterScanPackage(packageId, inspectSession)
+ *
{@link ProgressCheck#afterScanPackage(PackageId, Session)}
*
finishedScan()
*
{@link ProgressCheck#finishedScan()}
*
@@ -92,13 +104,18 @@ public final class ScriptProgressCheck implements ProgressCheck { public static final String BINDING_CHECK_CONFIG = "config"; public static final String FILENAME_INLINE_SCRIPT = "_inlineScript_"; public static final String INVOKE_ON_STARTED_SCAN = "startedScan"; + public static final String INVOKE_ON_SIMULATE_SLING = "simulateSling"; public static final String INVOKE_ON_IDENTIFY_PACKAGE = "identifyPackage"; - public static final String INVOKE_ON_IDENTIFY_SUBPACKAGE = "identifySubpackage"; public static final String INVOKE_ON_READ_MANIFEST = "readManifest"; public static final String INVOKE_ON_BEFORE_EXTRACT = "beforeExtract"; public static final String INVOKE_ON_IMPORTED_PATH = "importedPath"; public static final String INVOKE_ON_DELETED_PATH = "deletedPath"; public static final String INVOKE_ON_AFTER_EXTRACT = "afterExtract"; + public static final String INVOKE_ON_IDENTIFY_SUBPACKAGE = "identifySubpackage"; + public static final String INVOKE_ON_BEFORE_SLING_INSTALL = "beforeSlingInstall"; + public static final String INVOKE_ON_IDENTIFY_EMBEDDED_PACKAGE = "identifyEmbeddedPackage"; + public static final String INVOKE_ON_AFTER_SLING_INSTALL = "afterSlingInstall"; + public static final String INVOKE_ON_AFTER_SCAN_PACKAGE = "afterScanPackage"; public static final String INVOKE_ON_FINISHED_SCAN = "finishedScan"; public static final String INVOKE_GET_CHECK_NAME = "getCheckName"; @@ -223,6 +240,11 @@ void guardSessionHandler(final String methodName, final EventHandlerBody body) t } } + @Override + public void simulateSling(final SlingSimulator slingSimulator, final Set runModes) { + guardHandler(INVOKE_ON_SIMULATE_SLING, handle -> handle.apply(slingSimulator, runModes)); + } + @Override public void startedScan() { helper.collector.clearViolations(); @@ -234,10 +256,6 @@ public void identifyPackage(final PackageId packageId, final File file) { guardHandler(INVOKE_ON_IDENTIFY_PACKAGE, handle -> handle.apply(packageId, file)); } - @Override - public void identifySubpackage(final PackageId packageId, final PackageId parentId) { - guardHandler(INVOKE_ON_IDENTIFY_SUBPACKAGE, handle -> handle.apply(packageId, parentId)); - } @Override public void readManifest(final PackageId packageId, final Manifest manifest) { @@ -248,8 +266,9 @@ public void readManifest(final PackageId packageId, final Manifest manifest) { public void beforeExtract(final PackageId packageId, final Session inspectSession, final PackageProperties packageProperties, final MetaInf metaInf, final List subpackages) throws RepositoryException { - guardSessionHandler(INVOKE_ON_BEFORE_EXTRACT, handle -> handle.apply(packageId, inspectSession, packageProperties, - metaInf, subpackages.toArray(new PackageId[0]))); + guardSessionHandler(INVOKE_ON_BEFORE_EXTRACT, + handle -> handle.apply(packageId, inspectSession, packageProperties, + metaInf, subpackages.toArray(new PackageId[0]))); } @Override @@ -269,6 +288,39 @@ public void afterExtract(final PackageId packageId, final Session inspectSession guardSessionHandler(INVOKE_ON_AFTER_EXTRACT, handle -> handle.apply(packageId, inspectSession)); } + @Override + public void identifySubpackage(final PackageId packageId, final PackageId parentId) { + guardHandler(INVOKE_ON_IDENTIFY_SUBPACKAGE, handle -> handle.apply(packageId, parentId)); + } + + @Override + public void beforeSlingInstall(final PackageId lastPackage, + final SlingInstallable slingInstallable, + final Session inspectSession) throws RepositoryException { + guardSessionHandler(INVOKE_ON_BEFORE_SLING_INSTALL, + handle -> handle.apply(lastPackage, slingInstallable, inspectSession)); + } + + @Override + public void identifyEmbeddedPackage(final PackageId packageId, + final PackageId parentId, + final String jcrPath) { + guardHandler(INVOKE_ON_IDENTIFY_EMBEDDED_PACKAGE, handle -> handle.apply(packageId, parentId, jcrPath)); + } + + @Override + public void appliedRepoInitScripts(final PackageId lastPackage, + final SlingInstallable slingInstallable, + final Session inspectSession) throws RepositoryException { + guardSessionHandler(INVOKE_ON_AFTER_SLING_INSTALL, + handle -> handle.apply(lastPackage, slingInstallable, inspectSession)); + } + + @Override + public void afterScanPackage(final PackageId packageId, final Session inspectSession) throws RepositoryException { + guardSessionHandler(INVOKE_ON_AFTER_SCAN_PACKAGE, handle -> handle.apply(packageId, inspectSession)); + } + @Override public void finishedScan() { guardHandler(INVOKE_ON_FINISHED_SCAN, HandlerHandle::apply); diff --git a/core/src/main/java/net/adamcin/oakpal/core/SilencingCheckFacade.java b/core/src/main/java/net/adamcin/oakpal/core/SilencingCheckFacade.java new file mode 100644 index 000000000..5671d97bf --- /dev/null +++ b/core/src/main/java/net/adamcin/oakpal/core/SilencingCheckFacade.java @@ -0,0 +1,185 @@ +/* + * Copyright 2018 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.core; + +import net.adamcin.oakpal.api.PathAction; +import net.adamcin.oakpal.api.ProgressCheck; +import net.adamcin.oakpal.api.SilenceableCheck; +import net.adamcin.oakpal.api.SlingInstallable; +import net.adamcin.oakpal.api.SlingSimulator; +import net.adamcin.oakpal.api.Violation; +import org.apache.jackrabbit.vault.fs.config.MetaInf; +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.apache.jackrabbit.vault.packaging.PackageProperties; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import java.io.File; +import java.util.Collection; +import java.util.List; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.jar.Manifest; + +/** + * Internal facade class which serves to forcibly silence the wrapped {@link ProgressCheck} by not passing events when + * silenced. + */ +class SilencingCheckFacade implements SilenceableCheck { + + private final ProgressCheck wrapped; + private boolean silenced; + + SilencingCheckFacade(final @NotNull ProgressCheck wrapped) { + this.wrapped = wrapped; + } + + @Override + public String getCheckName() { + return wrapped.getCheckName(); + } + + @Override + public @Nullable String getResourceBundleBaseName() { + return wrapped.getResourceBundleBaseName(); + } + + @Override + public void setResourceBundle(final ResourceBundle resourceBundle) { + wrapped.setResourceBundle(resourceBundle); + } + + @Override + public void setSilenced(final boolean silenced) { + this.silenced = silenced; + } + + @Override + public Collection getReportedViolations() { + return wrapped.getReportedViolations(); + } + + @Override + public void simulateSling(final SlingSimulator slingSimulator, final Set runModes) { + wrapped.simulateSling(slingSimulator, runModes); + } + + @Override + public void startedScan() { + wrapped.startedScan(); + } + + @Override + public void finishedScan() { + wrapped.finishedScan(); + } + + //********************** + // SILENCEABLE EVENTS... + //********************** + + @Override + public void identifyPackage(final PackageId packageId, final File file) { + if (!silenced) { + wrapped.identifyPackage(packageId, file); + } + } + + @Override + public void readManifest(final PackageId packageId, final Manifest manifest) { + if (!silenced) { + wrapped.readManifest(packageId, manifest); + } + } + + @Override + public void beforeExtract(final PackageId packageId, final Session inspectSession, + final PackageProperties packageProperties, final MetaInf metaInf, + final List subpackages) throws RepositoryException { + if (!silenced) { + wrapped.beforeExtract(packageId, inspectSession, packageProperties, metaInf, subpackages); + } + } + + @Override + public void importedPath(final PackageId packageId, final String path, final Node node, + final PathAction action) throws RepositoryException { + if (!silenced) { + wrapped.importedPath(packageId, path, node, action); + } + } + + @Override + public void deletedPath(final PackageId packageId, final String path, final Session inspectSession) + throws RepositoryException { + if (!silenced) { + wrapped.deletedPath(packageId, path, inspectSession); + } + } + + @Override + public void afterExtract(final PackageId packageId, final Session inspectSession) throws RepositoryException { + if (!silenced) { + wrapped.afterExtract(packageId, inspectSession); + } + } + + @Override + public void identifySubpackage(final PackageId packageId, final PackageId parentId) { + if (!silenced) { + wrapped.identifySubpackage(packageId, parentId); + } + } + + @Override + public void beforeSlingInstall(final PackageId lastPackage, + final SlingInstallable slingInstallable, + final Session inspectSession) throws RepositoryException { + if (!silenced) { + wrapped.beforeSlingInstall(lastPackage, slingInstallable, inspectSession); + } + } + + @Override + public void identifyEmbeddedPackage(final PackageId packageId, final PackageId parentId, final String jcrPath) { + if (!silenced) { + wrapped.identifyEmbeddedPackage(packageId, parentId, jcrPath); + } + } + + @Override + public void appliedRepoInitScripts(final PackageId lastPackage, + final SlingInstallable slingInstallable, + final Session inspectSession) throws RepositoryException { + if (!silenced) { + wrapped.appliedRepoInitScripts(lastPackage, slingInstallable, inspectSession); + } + } + + @Override + public void afterScanPackage(final PackageId packageId, final Session inspectSession) throws RepositoryException { + if (!silenced) { + wrapped.afterScanPackage(packageId, inspectSession); + } + } + + +} + diff --git a/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java b/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java new file mode 100644 index 000000000..a1c85fd41 --- /dev/null +++ b/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java @@ -0,0 +1,101 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.core.checks; + +import net.adamcin.oakpal.api.PathAction; +import net.adamcin.oakpal.api.ProgressCheck; +import net.adamcin.oakpal.api.ProgressCheckFactory; +import net.adamcin.oakpal.api.SimpleProgressCheckFactoryCheck; +import net.adamcin.oakpal.api.SlingSimulator; +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.jetbrains.annotations.NotNull; +import org.osgi.annotation.versioning.ProviderType; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.json.JsonObject; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +public final class SlingJcrInstaller implements ProgressCheckFactory { + static final String DEFAULT_INSTALL_PATH_PATTERN = "^(/[^/]*)*/(install|config)$"; + + @ProviderType + public interface JsonKeys { + String rootPaths(); + } + + private static final JsonKeys KEYS = new JsonKeys() { + @Override + public String rootPaths() { + return "rootPaths"; + } + }; + + public static JsonKeys keys() { + return KEYS; + } + + @Override + public ProgressCheck newInstance(final JsonObject config) throws Exception { + return new Check(Arrays.asList("/apps", "/libs")); + } + + static final class Check extends SimpleProgressCheckFactoryCheck { + private final List rootPaths; + + private SlingSimulator slingSimulator; + private Pattern installPattern = Pattern.compile(DEFAULT_INSTALL_PATH_PATTERN); + + private List parentPaths = new ArrayList<>(); + + Check(final @NotNull List rootPaths) { + super(SlingJcrInstaller.class); + this.rootPaths = rootPaths; + } + + @Override + public void startedScan() { + super.startedScan(); + this.parentPaths.clear(); + } + + @Override + public void simulateSling(final SlingSimulator slingSimulator, final Set runModes) { + this.slingSimulator = slingSimulator; + } + + Pattern compileInstallPattern(final @NotNull Set runModes) { + return Pattern.compile(String.format("^(/[^/]*)*/(install|config)(.(%s))*$", + String.join("|", runModes))); + } + + @Override + public void importedPath(final PackageId packageId, final String path, final Node node, + final PathAction action) throws RepositoryException { + if (this.slingSimulator == null) { + return; + } + + + } + } + +} 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 new file mode 100644 index 000000000..ba040b89e --- /dev/null +++ b/core/src/main/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulator.java @@ -0,0 +1,336 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.core.sling; + +import net.adamcin.oakpal.api.EmbeddedPackageInstallable; +import net.adamcin.oakpal.api.Fun; +import net.adamcin.oakpal.api.RepoInitScriptsInstallable; +import net.adamcin.oakpal.api.Result; +import net.adamcin.oakpal.api.SlingInstallable; +import net.adamcin.oakpal.api.SlingSimulator; +import net.adamcin.oakpal.core.ErrorListener; +import org.apache.jackrabbit.vault.packaging.JcrPackage; +import org.apache.jackrabbit.vault.packaging.JcrPackageManager; +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.apache.jackrabbit.vault.packaging.VaultPackage; +import org.apache.jackrabbit.vault.packaging.impl.ZipVaultPackage; +import org.apache.sling.installer.api.InstallableResource; +import org.apache.sling.installer.core.impl.FileDataStore; +import org.apache.sling.installer.core.impl.InternalResource; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.framework.BundleContext; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Queue; +import java.util.function.Function; + +import static net.adamcin.oakpal.api.Fun.compose1; +import static net.adamcin.oakpal.api.Fun.result0; +import static net.adamcin.oakpal.api.Fun.result1; + +/** + * Noop implementation of a SlingSimulator. + */ +public final class DefaultSlingSimulator implements SlingSimulatorBackend, SlingSimulator { + public static SlingSimulatorBackend instance() { + return new DefaultSlingSimulator(); + } + + private Session session; + private JcrPackageManager packageManager; + private ErrorListener errorListener; + + private final Queue installables = new LinkedList<>(); + + @Override + public void startedScan() { + installables.clear(); + } + + @Override + public void setSession(final Session session) { + this.session = session; + } + + @Override + public void setPackageManager(final JcrPackageManager packageManager) { + this.packageManager = packageManager; + } + + @Override + public void setErrorListener(final ErrorListener errorListener) { + this.errorListener = errorListener; + } + + @Override + public @Nullable SlingInstallable dequeueInstallable() { + return installables.poll(); + } + + @Override + public @NotNull Iterable + openRepoInitScripts(@NotNull final RepoInitScriptsInstallable installable) { + return installable.getScripts(); + } + + @Override + public @Nullable Fun.ThrowingSupplier + openEmbeddedPackage(@NotNull final EmbeddedPackageInstallable installable) { + return () -> packageManager.open(session.getNode(installable.getJcrPath()), true); + } + + @Override + public @Nullable SlingInstallable prepareInstallableNode(final @NotNull PackageId parentPackageId, + final @NotNull Node node) { + final Result jcrPathResult = result0(node::getPath).get(); + final Result>> result = jcrPathResult + .flatMap(result1(session::getNode)) + .flatMap(DefaultSlingSimulator::readInternalResourceFromNode) + .map(resource -> resource.flatMap(DefaultSlingSimulator::createSlingInstallableParams)); + return jcrPathResult.flatMap(jcrPath -> + result.map(optParams -> + optParams.map(params -> params.createInstallable(parentPackageId, jcrPath)))) + .toOptional().flatMap(Function.identity()) + .orElse(null); + } + + @Override + public @Nullable SlingInstallable submitInstallable(final @NotNull SlingInstallable installable) { + installables.add(installable); + return installable; + } + + 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"; + + static @NotNull Result> + readInternalResourceFromNode(final @NotNull Node node) { + return result0(() -> { + final String path = node.getPath(); + if (Arrays.asList(node.getSession().getWorkspace().getNamespaceRegistry().getURIs()).contains(SLING_NS) + && node.isNodeType(SLING_OSGI_CONFIG)) { + final Dictionary props = new Hashtable<>(); + loadJcrProperties(props, node); + return Optional.of(new InstallableResource(path, null, props, "", null, 20)); + } else if (node.hasProperty(JCR_CONTENT_DATA)) { + final InputStream is = node.getProperty(JCR_CONTENT_DATA).getStream(); + final Dictionary dict = new Hashtable<>(); + dict.put(InstallableResource.INSTALLATION_HINT, node.getParent().getName()); + return Optional.of(new InstallableResource(path, is, dict, "", null, 200)); + } + return Optional.empty(); + }).get().flatMap(resource -> resource + .map(compose1( + DefaultSlingSimulator::readInternalResourceFromInstallableResource, + result -> result.map(Optional::of))) + .orElse(Result.success(Optional.empty()))); + } + + static @NotNull Result + readInternalResourceFromInstallableResource(final @NotNull InstallableResource resource) { + return result0(() -> InternalResource.create("jcrinstall", resource)).get(); + } + + static @NotNull Optional> + createSlingInstallableParams(final @NotNull InternalResource resource) { + SlingInstallableParams installable = null; + if (InstallableResource.TYPE_FILE.equals(resource.getType())) { + installable = maybePackageResource(resource); + if (installable != null) { + return Optional.of(installable); + } + installable = maybeBundleResource(resource); + if (installable != null) { + return Optional.of(installable); + } + } else if (InstallableResource.TYPE_PROPERTIES.equals(resource.getType())) { + // convert to OsgiConfigInstallable + installable = maybeConfigResource(resource); + if (installable != null) { + // if RepoInit, wrap with RepoInitScriptInstallable + return Optional.of(installable); + } + } + return Optional.empty(); + } + + static @Nullable EmbeddedPackageInstallableParams maybePackageResource(final @NotNull InternalResource resource) { + return result0(() -> new ZipVaultPackage(resource.getPrivateCopyOfFile(), true, false)).get() + .map(VaultPackage::getId) + .map(EmbeddedPackageInstallableParams::new).toOptional().orElse(null); + } + + static void setInternalDataStore(final @NotNull File dataDir) { + final BundleContext bundleContext = + (BundleContext) Proxy.newProxyInstance(DefaultSlingSimulator.class.getClassLoader(), + new Class[]{BundleContext.class}, + (Object proxy, Method method, Object[] args) -> { + if ("getDataFile".equals(method.getName())) { + return dataDir; + } + return null; + }); + new FileDataStore(bundleContext); + } + + static @Nullable OsgiBundleInstallableParams maybeBundleResource(final @NotNull InternalResource resource) { + return new OsgiBundleInstallableParams(); + } + + static String separatorsToUnix(final String path) { + if (path == null || path.indexOf('\\') == -1) { + return path; + } + return path.replace('\\', '/'); + } + + static String getResourceId(final String rawUrl) { + final String url = separatorsToUnix(rawUrl); + int pos = url.lastIndexOf('/'); + if (pos == -1) { + pos = url.indexOf(':'); + } + final String lastIdPart; + if (pos != -1) { + lastIdPart = url.substring(pos + 1); + } else { + lastIdPart = url; + } + return lastIdPart; + } + + private static final List EXTENSIONS = Arrays.asList(".config", ".properties", ".cfg", ".cfg.json"); + + private static String removeConfigExtension(final String id) { + for (final String ext : EXTENSIONS) { + if (id.endsWith(ext)) { + return id.substring(0, id.length() - ext.length()); + } + } + return id; + } + + static @Nullable OsgiConfigInstallableParams maybeConfigResource(final @NotNull InternalResource resource) { + final String lastIdPart = getResourceId(resource.getURL()); + + // remove extension if known + final String pid = removeConfigExtension(lastIdPart); + + // split pid and factory pid alias + final String factoryPid; + final String configPid; + int n = pid.indexOf('~'); + if (n == -1) { + n = pid.indexOf('-'); + } + if (n > 0) { + configPid = pid.substring(n + 1); + factoryPid = pid.substring(0, n); + } else { + factoryPid = null; + configPid = pid; + } + + final Map properties = new HashMap<>(); + Optional.ofNullable(resource.getPrivateCopyOfDictionary()).ifPresent(dict -> { + for (final Enumeration keys = dict.keys(); keys.hasMoreElements(); ) { + final String key = keys.nextElement(); + properties.put(key, dict.get(key)); + } + }); + + return new OsgiConfigInstallableParams(properties, configPid, factoryPid); + } + + static void loadJcrProperties(final @NotNull Dictionary configMap, final @NotNull Node configNode) + throws RepositoryException { + final PropertyIterator pi = configNode.getProperties(); + while (pi.hasNext()) { + final Property p = pi.nextProperty(); + final String name = p.getName(); + + // ignore jcr: and similar properties + if (name.contains(":")) { + continue; + } + if (p.getDefinition().isMultiple()) { + Object[] data = null; + final Value[] values = p.getValues(); + int i = 0; + for (Value v : values) { + Object o = convertJcrValue(v); + if (o != null) { + if (i == 0) { + data = (Object[]) Array.newInstance(o.getClass(), values.length); + } + data[i++] = o; + } + } + // create empty array in case no value is specified + if (data == null) { + data = new String[0]; + } + configMap.put(name, data); + + } else { + final Object o = convertJcrValue(p.getValue()); + if (o != null) { + configMap.put(name, o); + } + } + } + } + + static @Nullable Object convertJcrValue(final @NotNull Value value) throws RepositoryException { + switch (value.getType()) { + case PropertyType.STRING: + return value.getString(); + case PropertyType.DATE: + return value.getDate(); + case PropertyType.DOUBLE: + return value.getDouble(); + case PropertyType.LONG: + return value.getLong(); + case PropertyType.BOOLEAN: + return value.getBoolean(); + } + return null; + } + +} diff --git a/core/src/main/java/net/adamcin/oakpal/core/sling/EmbeddedPackageInstallableParams.java b/core/src/main/java/net/adamcin/oakpal/core/sling/EmbeddedPackageInstallableParams.java new file mode 100644 index 000000000..2a19440b0 --- /dev/null +++ b/core/src/main/java/net/adamcin/oakpal/core/sling/EmbeddedPackageInstallableParams.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.core.sling; + +import net.adamcin.oakpal.api.EmbeddedPackageInstallable; +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.jetbrains.annotations.NotNull; + +public class EmbeddedPackageInstallableParams implements SlingInstallableParams { + private final @NotNull PackageId embeddedId; + + public EmbeddedPackageInstallableParams(@NotNull final PackageId embeddedId) { + this.embeddedId = embeddedId; + } + + @NotNull + @Override + public EmbeddedPackageInstallable createInstallable(final PackageId parentPackageId, final String jcrPath) { + return new EmbeddedPackageInstallable(parentPackageId, jcrPath, embeddedId); + } +} diff --git a/core/src/main/java/net/adamcin/oakpal/core/sling/InternalResource.java b/core/src/main/java/net/adamcin/oakpal/core/sling/InternalResource.java new file mode 100644 index 000000000..f2d48cecb --- /dev/null +++ b/core/src/main/java/net/adamcin/oakpal/core/sling/InternalResource.java @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.core.sling; + +public class InternalResource { +} diff --git a/core/src/main/java/net/adamcin/oakpal/core/sling/NoopSlingSimulator.java b/core/src/main/java/net/adamcin/oakpal/core/sling/NoopSlingSimulator.java new file mode 100644 index 000000000..3bff85a06 --- /dev/null +++ b/core/src/main/java/net/adamcin/oakpal/core/sling/NoopSlingSimulator.java @@ -0,0 +1,86 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.core.sling; + +import net.adamcin.oakpal.api.EmbeddedPackageInstallable; +import net.adamcin.oakpal.api.Fun; +import net.adamcin.oakpal.api.RepoInitScriptsInstallable; +import net.adamcin.oakpal.api.SlingInstallable; +import net.adamcin.oakpal.api.SlingSimulator; +import net.adamcin.oakpal.core.ErrorListener; +import org.apache.jackrabbit.vault.packaging.JcrPackage; +import org.apache.jackrabbit.vault.packaging.JcrPackageManager; +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.jcr.Node; +import javax.jcr.Session; +import java.util.Collections; + +/** + * Noop implementation of a SlingSimulator. + */ +public final class NoopSlingSimulator implements SlingSimulatorBackend, SlingSimulator { + public static SlingSimulatorBackend instance() { + return new NoopSlingSimulator(); + } + + @Override + public void setSession(final Session session) { + /* do nothing */ + } + + @Override + public void setPackageManager(final JcrPackageManager packageManager) { + /* do nothing */ + } + + @Override + public void setErrorListener(final ErrorListener errorListener) { + /* do nothing */ + } + + @Override + public @Nullable SlingInstallable dequeueInstallable() { + return null; + } + + @Override + public @NotNull Iterable + openRepoInitScripts(@NotNull final RepoInitScriptsInstallable installable) { + return Collections.emptyList(); + } + + @Override + public @Nullable Fun.ThrowingSupplier + openEmbeddedPackage(@NotNull final EmbeddedPackageInstallable installable) { + return null; + } + + @Override + public @Nullable SlingInstallable prepareInstallableNode(final @NotNull PackageId parentPackageId, + final @NotNull Node node) { + return null; + } + + @Override + public @Nullable SlingInstallable submitInstallable(final @NotNull SlingInstallable installable) { + return null; + } + +} diff --git a/core/src/main/java/net/adamcin/oakpal/core/sling/OsgiBundleInstallableParams.java b/core/src/main/java/net/adamcin/oakpal/core/sling/OsgiBundleInstallableParams.java new file mode 100644 index 000000000..40d171145 --- /dev/null +++ b/core/src/main/java/net/adamcin/oakpal/core/sling/OsgiBundleInstallableParams.java @@ -0,0 +1,51 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.core.sling; + +import net.adamcin.oakpal.api.OsgiBundleInstallable; +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.jar.Manifest; + +public class OsgiBundleInstallableParams implements SlingInstallableParams { + private final @Nullable String installationHint; + private final @NotNull Manifest manifest; + private final @NotNull String symbolicName; + private final @NotNull String version; + private final @Nullable String activationPolicy; + + public OsgiBundleInstallableParams(@Nullable final String installationHint, + @NotNull final Manifest manifest, + @NotNull final String symbolicName, + @NotNull final String version, + @Nullable final String activationPolicy) { + this.installationHint = installationHint; + this.manifest = manifest; + this.symbolicName = symbolicName; + this.version = version; + this.activationPolicy = activationPolicy; + } + + @NotNull + @Override + public OsgiBundleInstallable createInstallable(final PackageId parentPackageId, final String jcrPath) { + return new OsgiBundleInstallable(parentPackageId, jcrPath, installationHint, + manifest, symbolicName, version, activationPolicy); + } +} diff --git a/core/src/main/java/net/adamcin/oakpal/core/sling/OsgiConfigInstallableParams.java b/core/src/main/java/net/adamcin/oakpal/core/sling/OsgiConfigInstallableParams.java new file mode 100644 index 000000000..bd32aa64e --- /dev/null +++ b/core/src/main/java/net/adamcin/oakpal/core/sling/OsgiConfigInstallableParams.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.core.sling; + +import net.adamcin.oakpal.api.OsgiConfigInstallable; +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class OsgiConfigInstallableParams implements SlingInstallableParams { + private final @NotNull Map properties; + private final @NotNull String servicePid; + private final @Nullable String factoryPid; + + public OsgiConfigInstallableParams(@NotNull final Map properties, + @NotNull final String servicePid, + @Nullable final String factoryPid) { + this.properties = Collections.unmodifiableMap(new HashMap<>(properties)); + this.servicePid = servicePid; + this.factoryPid = factoryPid; + } + + @NotNull + @Override + public OsgiConfigInstallable createInstallable(final PackageId parentPackageId, final String jcrPath) { + return new OsgiConfigInstallable(parentPackageId, jcrPath, properties, servicePid, factoryPid); + } +} diff --git a/core/src/main/java/net/adamcin/oakpal/core/sling/SlingInstallableParams.java b/core/src/main/java/net/adamcin/oakpal/core/sling/SlingInstallableParams.java new file mode 100644 index 000000000..b64ac7a73 --- /dev/null +++ b/core/src/main/java/net/adamcin/oakpal/core/sling/SlingInstallableParams.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.core.sling; + +import net.adamcin.oakpal.api.SlingInstallable; +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.jetbrains.annotations.NotNull; +import org.osgi.annotation.versioning.ProviderType; + +/** + * Encapsulation of parameters that + * @param sling installable type that will be created + */ +@ProviderType +public interface SlingInstallableParams { + @NotNull T createInstallable(PackageId parentPackageId, String jcrPath); +} diff --git a/core/src/main/java/net/adamcin/oakpal/core/sling/SlingSimulatorBackend.java b/core/src/main/java/net/adamcin/oakpal/core/sling/SlingSimulatorBackend.java new file mode 100644 index 000000000..07bd17342 --- /dev/null +++ b/core/src/main/java/net/adamcin/oakpal/core/sling/SlingSimulatorBackend.java @@ -0,0 +1,88 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.core.sling; + +import net.adamcin.oakpal.api.EmbeddedPackageInstallable; +import net.adamcin.oakpal.api.Fun; +import net.adamcin.oakpal.api.RepoInitScriptsInstallable; +import net.adamcin.oakpal.api.ScanListener; +import net.adamcin.oakpal.api.SlingInstallable; +import net.adamcin.oakpal.api.SlingSimulator; +import net.adamcin.oakpal.core.ErrorListener; +import org.apache.jackrabbit.vault.packaging.JcrPackage; +import org.apache.jackrabbit.vault.packaging.JcrPackageManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; + +import javax.jcr.Session; + +/** + * Interface defining internal-facing behavior for the SlingSimulator, primarily regarding the retrieval and processing + * of Sling JCR installable resources submitted by ProgressChecks. + */ +@ProviderType +public interface SlingSimulatorBackend extends SlingSimulator, ScanListener { + + /** + * Provide a JCR session. + * + * @param session the jcr session + */ + void setSession(Session session); + + /** + * Provide a JCR package manager. + * + * @param packageManager the jcr package manager + */ + void setPackageManager(JcrPackageManager packageManager); + + /** + * Provide an ErrorListener. + * + * @param errorListener the error listener + */ + void setErrorListener(ErrorListener errorListener); + + /** + * Get the collected installable paths. The consumer of this method is instructed to treat the returned map as an + * iterable. Each entry gives the path as a key, and the installable type as the value. The type enum should be used + * by the consumer to call the appropriate get method + * + * @return a resource to be installed immediately or null to continue the scan + */ + @Nullable SlingInstallable dequeueInstallable(); + + /** + * Get installable, raw repoinit scripts from the specified JCR path. + * + * @param installable the installable + * @return an iterable of raw repoinit scripts + */ + @NotNull Iterable + openRepoInitScripts(@NotNull RepoInitScriptsInstallable installable); + + /** + * Get installable JCR package from the specified JCR path. + * + * @param installable the subpackage installable + * @return a subpackage supplier or null + */ + @Nullable Fun.ThrowingSupplier + openEmbeddedPackage(@NotNull EmbeddedPackageInstallable installable); +} diff --git a/core/src/test/java/net/adamcin/oakpal/core/ErrorListenerTest.java b/core/src/test/java/net/adamcin/oakpal/core/ErrorListenerTest.java index a494475c9..887ce4bc1 100644 --- a/core/src/test/java/net/adamcin/oakpal/core/ErrorListenerTest.java +++ b/core/src/test/java/net/adamcin/oakpal/core/ErrorListenerTest.java @@ -46,6 +46,8 @@ public Collection getReportedViolations() { mock.onProhibitedInstallHookRegistration(null); mock.onRepoInitUrlError(null, null); mock.onRepoInitInlineError(null, null); + mock.onSlingRepoInitScriptsError(null, null, null); + mock.onSlingEmbeddedPackageError(null, null); mock.finishedScan(); } } diff --git a/core/src/test/java/net/adamcin/oakpal/core/OakMachineTest.java b/core/src/test/java/net/adamcin/oakpal/core/OakMachineTest.java index a4b9793a8..53648dc67 100644 --- a/core/src/test/java/net/adamcin/oakpal/core/OakMachineTest.java +++ b/core/src/test/java/net/adamcin/oakpal/core/OakMachineTest.java @@ -19,7 +19,11 @@ import junitx.util.PrivateAccessor; import net.adamcin.oakpal.api.PathAction; import net.adamcin.oakpal.api.ProgressCheck; +import net.adamcin.oakpal.api.SilenceableCheck; import net.adamcin.oakpal.api.SimpleProgressCheck; +import net.adamcin.oakpal.api.EmbeddedPackageInstallable; +import net.adamcin.oakpal.api.RepoInitScriptsInstallable; +import net.adamcin.oakpal.core.sling.SlingSimulatorBackend; import net.adamcin.oakpal.testing.TestPackageUtil; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore; @@ -55,6 +59,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; +import java.io.Reader; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; @@ -66,8 +71,12 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Properties; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.jar.Manifest; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -79,6 +88,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -157,6 +167,14 @@ public void testBuildWithInitStage() throws Exception { }); } + @Test + public void testBuildWithInstallWatcher() throws Exception { + final File testPackage = TestPackageUtil.prepareTestPackage("null-dependency-test.zip"); + final SlingSimulatorBackend watcher = mock(SlingSimulatorBackend.class); + assertTrue("no errors", builder().withSlingSimulator(watcher).build() + .scanPackage(testPackage).get(0).getViolations().isEmpty()); + } + @Test public void testBuildWithErrorListener() throws Exception { final CompletableFuture> latch = new CompletableFuture<>(); @@ -616,6 +634,253 @@ public void testProcessSubpackage_onSubpackageException() throws Exception { } + @Test(expected = RepositoryException.class) + public void testProcessSubpackage_bubbledRepositoryException() throws Exception { + final JcrPackageManager manager = mock(JcrPackageManager.class); + doThrow(RepositoryException.class).when(manager).open(any(PackageId.class)); + final PackageId root = PackageId.fromString("my_packages:subsubtest"); + final PackageId sub1 = PackageId.fromString("my_packages:subtest"); + final Session session = mock(Session.class); + doThrow(RepositoryException.class).when(session).refresh(anyBoolean()); + final CompletableFuture eLatch = new CompletableFuture<>(); + final CompletableFuture idLatch = new CompletableFuture<>(); + final ErrorListener errorListener = mock(ErrorListener.class); + doAnswer(call -> { + eLatch.complete(call.getArgument(0, Exception.class)); + idLatch.complete(call.getArgument(1, PackageId.class)); + return true; + }).when(errorListener).onSubpackageException(any(Exception.class), any(PackageId.class)); + builder().withErrorListener(errorListener).build() + .processSubpackage(session, manager, sub1, root, false); + } + + @Test + public void testProcessSubpackageInstallable_onSubpackageException() throws Exception { + final JcrPackageManager manager = mock(JcrPackageManager.class); + doThrow(RepositoryException.class).when(manager).open(any(PackageId.class)); + final PackageId root = PackageId.fromString("my_packages:subsubtest"); + final PackageId sub1 = PackageId.fromString("my_packages:subtest"); + final String jcrPath = "/some/path"; + final EmbeddedPackageInstallable installable = new EmbeddedPackageInstallable(root, jcrPath, sub1); + final Session session = mock(Session.class); + final CompletableFuture eLatch = new CompletableFuture<>(); + final CompletableFuture installableLatch = new CompletableFuture<>(); + final ErrorListener errorListener = mock(ErrorListener.class); + doAnswer(call -> { + eLatch.complete(call.getArgument(0, Exception.class)); + installableLatch.complete(call.getArgument(1, EmbeddedPackageInstallable.class)); + return true; + }).when(errorListener).onSlingEmbeddedPackageError(any(Exception.class), + any(EmbeddedPackageInstallable.class)); + + final SlingSimulatorBackend installWatcher = mock(SlingSimulatorBackend.class); + when(installWatcher.openEmbeddedPackage(installable)) + .thenReturn(() -> manager.open(sub1)); + builder() + .withSlingSimulator(installWatcher) + .withErrorListener(errorListener).build() + .processEmbeddedPackage(session, manager, installable, false); + assertTrue("error is of type", eLatch.getNow(null) instanceof RepositoryException); + assertSame("expect same installable", installable, installableLatch.getNow(null)); + } + + @Test(expected = RepositoryException.class) + public void testProcessSubpackageInstallable_bubbledRepositoryException() throws Exception { + final JcrPackageManager manager = mock(JcrPackageManager.class); + doThrow(RepositoryException.class).when(manager).open(any(PackageId.class)); + final PackageId root = PackageId.fromString("my_packages:subsubtest"); + final PackageId sub1 = PackageId.fromString("my_packages:subtest"); + final String jcrPath = "/some/path"; + final EmbeddedPackageInstallable installable = new EmbeddedPackageInstallable(root, jcrPath, sub1); + final Session session = mock(Session.class); + doThrow(RepositoryException.class).when(session).refresh(anyBoolean()); + final CompletableFuture eLatch = new CompletableFuture<>(); + final CompletableFuture idLatch = new CompletableFuture<>(); + final CompletableFuture installableLatch = new CompletableFuture<>(); + final ErrorListener errorListener = mock(ErrorListener.class); + doAnswer(call -> { + eLatch.complete(call.getArgument(0, Exception.class)); + installableLatch.complete(call.getArgument(1, EmbeddedPackageInstallable.class)); + return true; + }).when(errorListener).onSlingEmbeddedPackageError(any(Exception.class), + any(EmbeddedPackageInstallable.class)); + + final SlingSimulatorBackend installWatcher = mock(SlingSimulatorBackend.class); + when(installWatcher.openEmbeddedPackage(installable)) + .thenReturn(() -> manager.open(sub1)); + builder() + .withSlingSimulator(installWatcher) + .withErrorListener(errorListener).build() + .processEmbeddedPackage(session, manager, installable, false); + } + + @Test + public void testInternalProcessSubpackage_nullPackage() throws Exception { + final JcrPackageManager manager = mock(JcrPackageManager.class); + when(manager.open(any(PackageId.class))).thenReturn(null); + final PackageId root = PackageId.fromString("my_packages:subsubtest"); + final PackageId sub1 = PackageId.fromString("my_packages:subtest"); + final CompletableFuture refreshedLatch = new CompletableFuture<>(); + final Session session = mock(Session.class); + doAnswer(call -> refreshedLatch.complete(call.getArgument(0))) + .when(session).refresh(anyBoolean()); + final CompletableFuture eLatch = new CompletableFuture<>(); + final CompletableFuture idLatch = new CompletableFuture<>(); + final ErrorListener errorListener = mock(ErrorListener.class); + doAnswer(call -> { + eLatch.complete(call.getArgument(0, Exception.class)); + idLatch.complete(call.getArgument(1, PackageId.class)); + return true; + }).when(errorListener).onSubpackageException(any(Exception.class), any(PackageId.class)); + builder().withErrorListener(errorListener).build() + .internalProcessSubpackage(session, manager, sub1, false, + () -> manager.open(sub1), check -> check.identifySubpackage(sub1, root), + error -> errorListener.onSubpackageException(error, sub1)); + assertTrue("error is of type", eLatch.getNow(null) instanceof PackageException); + assertEquals("package id is", sub1, idLatch.getNow(null)); + assertFalse("expect session.refresh(false)", refreshedLatch.getNow(true)); + } + + @Test + public void testInternalProcessSubpackage_throwsRuntimeException() throws Exception { + final JcrPackageManager manager = mock(JcrPackageManager.class); + doThrow(RuntimeException.class).when(manager).open(any(PackageId.class)); + final PackageId root = PackageId.fromString("my_packages:subsubtest"); + final PackageId sub1 = PackageId.fromString("my_packages:subtest"); + final CompletableFuture refreshedLatch = new CompletableFuture<>(); + final Session session = mock(Session.class); + doAnswer(call -> refreshedLatch.complete(call.getArgument(0))) + .when(session).refresh(anyBoolean()); + final CompletableFuture eLatch = new CompletableFuture<>(); + final CompletableFuture idLatch = new CompletableFuture<>(); + final ErrorListener errorListener = mock(ErrorListener.class); + doAnswer(call -> { + eLatch.complete(call.getArgument(0, Exception.class)); + idLatch.complete(call.getArgument(1, PackageId.class)); + return true; + }).when(errorListener).onSubpackageException(any(Exception.class), any(PackageId.class)); + + builder().withErrorListener(errorListener).build() + .internalProcessSubpackage(session, manager, sub1, false, + () -> manager.open(sub1), check -> check.identifySubpackage(sub1, root), + error -> errorListener.onSubpackageException(error, sub1)); + assertTrue("error is of type", eLatch.getNow(null) instanceof RuntimeException); + assertEquals("package id is", sub1, idLatch.getNow(null)); + assertFalse("expect not called session.refresh(false)", refreshedLatch.isDone()); + session.refresh(true); + assertTrue("expect session.refresh(true) on cleanup", refreshedLatch.getNow(false)); + } + + @Test + public void testProcessInstallableQueue_noop() throws Exception { + final JcrPackageManager manager = mock(JcrPackageManager.class); + final Session session = mock(Session.class); + final PackageId root = PackageId.fromString("my_packages:subsubtest"); + new OakMachine.Builder().build().processInstallableQueue(session, manager, root, false); + } + + @Test + public void testProcessInstallableQueue_singleSubpackageInstallable() throws Exception { + final JcrPackageManager manager = mock(JcrPackageManager.class); + final Session session = mock(Session.class); + final PackageId root = PackageId.fromString("my_packages:subsubtest"); + final PackageId sub1 = PackageId.fromString("my_packages:subtest"); + final String jcrPath = "/some/path"; + final EmbeddedPackageInstallable installable = new EmbeddedPackageInstallable(root, jcrPath, sub1); + final List installables = new ArrayList<>(); + installables.add(installable); + final SlingSimulatorBackend installWatcher = mock(SlingSimulatorBackend.class); + final List> dequeuedValues = new ArrayList<>(); + doAnswer(call -> { + if (installables.isEmpty()) { + dequeuedValues.add(Optional.empty()); + return null; + } else { + EmbeddedPackageInstallable toReturn = installables.remove(0); + dequeuedValues.add(Optional.ofNullable(toReturn)); + return toReturn; + } + }).when(installWatcher).dequeueInstallable(); + + final CompletableFuture openedSlot = new CompletableFuture<>(); + doAnswer(call -> { + openedSlot.complete(call.getArgument(0)); + return null; + }).when(installWatcher).openEmbeddedPackage(installable); + + new OakMachine.Builder() + .withSlingSimulator(installWatcher) + .build().processInstallableQueue(session, manager, root, false); + + assertEquals("expect dequeued values", + Arrays.asList(Optional.of(installable), Optional.empty()), dequeuedValues); + assertSame("expect request to open installable", installable, openedSlot.getNow(null)); + } + + @Test + public void testProcessInstallableQueue_singleRepoInitInstallable() throws Exception { + final JcrPackageManager manager = mock(JcrPackageManager.class); + final Session session = mock(Session.class); + final PackageId root = PackageId.fromString("my_packages:subsubtest"); + final RepoInitScriptsInstallable installable = new RepoInitScriptsInstallable(root, "/repoInit", + Collections.emptyList()); + final List installables = new ArrayList<>(); + installables.add(installable); + final SlingSimulatorBackend installWatcher = mock(SlingSimulatorBackend.class); + final List> dequeuedValues = new ArrayList<>(); + + doAnswer(call -> { + if (installables.isEmpty()) { + dequeuedValues.add(Optional.empty()); + return null; + } else { + RepoInitScriptsInstallable toReturn = installables.remove(0); + dequeuedValues.add(Optional.ofNullable(toReturn)); + return toReturn; + } + }).when(installWatcher).dequeueInstallable(); + + final CompletableFuture readerSlot = new CompletableFuture<>(); + final OakMachine.RepoInitProcessor repoInitProcessor = mock(OakMachine.RepoInitProcessor.class); + doAnswer(call -> readerSlot.complete(call.getArgument(1))) + .when(repoInitProcessor).apply(any(Session.class), any(Reader.class)); + + final CompletableFuture openedSlot = new CompletableFuture<>(); + doAnswer(call -> { + openedSlot.complete(call.getArgument(0)); + List scripts = new ArrayList<>(); + scripts.add("create user testProcessInstallableQueue_singleRepoInitInstallable"); + scripts.add(null); + return scripts; + }).when(installWatcher).openRepoInitScripts(installable); + + final CompletableFuture eLatch = new CompletableFuture<>(); + final CompletableFuture scriptLatch = new CompletableFuture<>(); + final CompletableFuture installableLatch = new CompletableFuture<>(); + final ErrorListener errorListener = mock(ErrorListener.class); + doAnswer(call -> { + eLatch.complete(call.getArgument(0, Throwable.class)); + scriptLatch.complete(call.getArgument(1, String.class)); + installableLatch.complete(call.getArgument(2, RepoInitScriptsInstallable.class)); + return true; + }).when(errorListener).onSlingRepoInitScriptsError(any(Throwable.class), nullable(String.class), + any(RepoInitScriptsInstallable.class)); + + new OakMachine.Builder() + .withErrorListener(errorListener) + .withRepoInitProcesser(repoInitProcessor) + .withSlingSimulator(installWatcher) + .build().processInstallableQueue(session, manager, root, false); + + assertEquals("expect dequeued values", + Arrays.asList(Optional.of(installable), Optional.empty()), dequeuedValues); + assertSame("expect request to open installable", installable, openedSlot.getNow(null)); + assertTrue("expect reader is complete", readerSlot.isDone()); + assertTrue("error is of type", eLatch.getNow(null) instanceof NullPointerException); + assertNull("failedScript is", scriptLatch.getNow("not failed")); + assertSame("expect same installable in error", installable, installableLatch.getNow(null)); + } + @Test(expected = AbortedScanException.class) public void testProcessPackageUrl_abortOnRefreshFailure() throws Exception { final File testPackage = TestPackageUtil.prepareTestPackage("tmp_foo_bar.zip"); @@ -646,6 +911,83 @@ public void testProcessPackageFile_abortOnRefreshFailure() throws Exception { builder().build().processPackageFile(session, manager, true, testPackage); } + @Test + public void testNewProgressCheckEventConsumer() { + final List> errorEvents = new ArrayList<>(); + + final BiConsumer onError = + (check, error) -> errorEvents.add(toEntry(check, error)); + + final Consumer silencedConsumer = + OakMachine.newProgressCheckEventConsumer(true, + ProgressCheck::finishedScan, onError); + final Consumer vocalConsumer = + OakMachine.newProgressCheckEventConsumer(false, + ProgressCheck::finishedScan, onError); + + final AtomicInteger normalCallCount = new AtomicInteger(0); + final ProgressCheck normalCheck = mock(ProgressCheck.class); + doAnswer(call -> normalCallCount.incrementAndGet()).when(normalCheck).finishedScan(); + silencedConsumer.accept(normalCheck); + assertEquals("expect normal:silent empty errors", 0, errorEvents.size()); + assertEquals("expect normal:silent call count", 0, normalCallCount.get()); + errorEvents.clear(); + normalCallCount.set(0); + vocalConsumer.accept(normalCheck); + assertEquals("expect normal:vocal empty errors", 0, errorEvents.size()); + assertEquals("expect normal:vocal call count", 1, normalCallCount.get()); + errorEvents.clear(); + normalCallCount.set(0); + doThrow(RuntimeException.class).when(normalCheck).finishedScan(); + silencedConsumer.accept(normalCheck); + assertEquals("expect normal:silent empty errors", 0, errorEvents.size()); + assertEquals("expect normal:silent call count", 0, normalCallCount.get()); + errorEvents.clear(); + normalCallCount.set(0); + vocalConsumer.accept(normalCheck); + assertEquals("expect normal:vocal one error", 1, errorEvents.size()); + assertEquals("expect normal:vocal call count", 0, normalCallCount.get()); + errorEvents.clear(); + normalCallCount.set(0); + + final AtomicInteger watcherCallCount = new AtomicInteger(0); + final List silencedEvents = new ArrayList<>(); + final SilenceableCheck silenceableCheck = mock(SilenceableCheck.class); + doAnswer(call -> silencedEvents.add(call.getArgument(0))) + .when(silenceableCheck).setSilenced(anyBoolean()); + doAnswer(call -> watcherCallCount.incrementAndGet()).when(silenceableCheck).finishedScan(); + silencedConsumer.accept(silenceableCheck); + assertEquals("expect watcher:silent empty errors", 0, errorEvents.size()); + assertEquals("expect watcher:silent call count", 1, watcherCallCount.get()); + assertEquals("expect watcher:silent silenced events", + Arrays.asList(true, false), silencedEvents); + errorEvents.clear(); + watcherCallCount.set(0); + silencedEvents.clear(); + vocalConsumer.accept(silenceableCheck); + assertEquals("expect watcher:vocal empty errors", 0, errorEvents.size()); + assertEquals("expect watcher:vocal call count", 1, watcherCallCount.get()); + assertEquals("expect watcher:vocal silenced events", + Collections.emptyList(), silencedEvents); + errorEvents.clear(); + watcherCallCount.set(0); + silencedEvents.clear(); + doThrow(RuntimeException.class).when(silenceableCheck).finishedScan(); + silencedConsumer.accept(silenceableCheck); + assertEquals("expect watcher:silent empty errors", 0, errorEvents.size()); + assertEquals("expect watcher:silent call count", 0, watcherCallCount.get()); + assertEquals("expect watcher:silent silenced events", + Arrays.asList(true, false), silencedEvents); + errorEvents.clear(); + watcherCallCount.set(0); + silencedEvents.clear(); + vocalConsumer.accept(silenceableCheck); + assertEquals("expect watcher:vocal one error", 1, errorEvents.size()); + assertEquals("expect watcher:vocal call count", 0, watcherCallCount.get()); + assertEquals("expect watcher:vocal silenced events", + Collections.emptyList(), silencedEvents); + } + @Test public void testImporterListenerAdapter_onMessage_deletedPathException() throws Exception { final File testPackage = TestPackageUtil.prepareTestPackage("tmp_foo_bar.zip"); @@ -730,8 +1072,8 @@ public void testImporterListenerAdapter_onError() { return true; }).when(errorListener).onImporterException(any(Exception.class), any(PackageId.class), anyString()); final OakMachine machine = builder().withErrorListener(errorListener).build(); - final OakMachine.ImporterListenerAdapter adapter = machine.new ImporterListenerAdapter(expectId, - machine.getProgressChecks(), session, false); + final OakMachine.ImporterListenerAdapter adapter = + machine.new ImporterListenerAdapter(expectId, session, false); adapter.onError(ProgressTrackerListener.Mode.PATHS, expectPath, expectError); assertSame("error is same", expectError, eLatch.getNow(null)); assertEquals("package id is", expectId, idLatch.getNow(null)); @@ -756,8 +1098,8 @@ public void testImporterListenerAdapter_onMessage_error() throws Exception { return true; }).when(errorListener).onImporterException(any(Exception.class), any(PackageId.class), anyString()); final OakMachine machine = builder().withErrorListener(errorListener).build(); - final OakMachine.ImporterListenerAdapter adapter = machine.new ImporterListenerAdapter(expectId, - machine.getProgressChecks(), session, false); + final OakMachine.ImporterListenerAdapter adapter = + machine.new ImporterListenerAdapter(expectId, session, false); adapter.onMessage(ProgressTrackerListener.Mode.PATHS, "E", expectPath); assertEquals("package id is", expectId, idLatch.getNow(null)); assertEquals("path is", expectPath, pathLatch.getNow(null)); @@ -819,30 +1161,30 @@ public void testFileBlobMemoryNodeStore() throws Exception { new FileBlobMemoryNodeStore(blobStoreFile.getAbsolutePath())); machineBuilder.build().adminInitAndInspect(session -> { - // less than AbtractBlobStore.minBlockSize - 2 (for varInt length prefix where length >= 128 && < 16384) - final Binary binary = alphaFill(session, 4093); + // less than AbtractBlobStore.minBlockSize - 2 (for varInt length prefix where length >= 128 && < 16384) + final Binary binary = alphaFill(session, 4093); - Node fooNode = session.getRootNode().addNode("foo", "nt:unstructured"); - fooNode.setProperty("data", binary); - session.save(); + Node fooNode = session.getRootNode().addNode("foo", "nt:unstructured"); + fooNode.setProperty("data", binary); + session.save(); - assertTrue("blob is retrievable", fooNode.getProperty("data").getString().startsWith("abcdefg")); - }); + assertTrue("blob is retrievable", fooNode.getProperty("data").getString().startsWith("abcdefg")); + }); final File[] inlineChildren = blobStoreFile.listFiles(); assertNotNull("should have non-null inlineChildren", inlineChildren); assertEquals("inline children is empty <4k", 0, inlineChildren.length); machineBuilder.build().adminInitAndInspect(session -> { - // bigger than 4096 - final Binary binary = alphaFill(session, 8192); - assertFalse("/foo should not exist", session.nodeExists("/foo")); - Node fooNode = session.getRootNode().addNode("foo", "nt:unstructured"); - fooNode.setProperty("data", binary); - session.save(); - - assertTrue("blob is retrievable", fooNode.getProperty("data").getString().startsWith("abcdefg")); - }); + // bigger than 4096 + final Binary binary = alphaFill(session, 8192); + assertFalse("/foo should not exist", session.nodeExists("/foo")); + Node fooNode = session.getRootNode().addNode("foo", "nt:unstructured"); + fooNode.setProperty("data", binary); + session.save(); + + assertTrue("blob is retrievable", fooNode.getProperty("data").getString().startsWith("abcdefg")); + }); final File[] blobChildren = blobStoreFile.listFiles(); assertNotNull("should have non-null blobChildren", blobChildren); @@ -863,5 +1205,7 @@ private static Binary alphaFill(final @NotNull Session session, final int bufSiz return session.getValueFactory().createBinary(new ByteArrayInputStream(buffer)); } + + } diff --git a/core/src/test/java/net/adamcin/oakpal/core/ScriptProgressCheckTest.java b/core/src/test/java/net/adamcin/oakpal/core/ScriptProgressCheckTest.java index e4b959c9f..fb108307b 100644 --- a/core/src/test/java/net/adamcin/oakpal/core/ScriptProgressCheckTest.java +++ b/core/src/test/java/net/adamcin/oakpal/core/ScriptProgressCheckTest.java @@ -40,15 +40,12 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.PropertyResourceBundle; -import java.util.ResourceBundle; import java.util.jar.Manifest; import static net.adamcin.oakpal.api.Fun.toEntry; -import static net.adamcin.oakpal.api.Fun.tryOrDefault1; import static net.adamcin.oakpal.api.JavaxJson.key; import static net.adamcin.oakpal.api.JavaxJson.obj; import static org.junit.Assert.assertEquals; @@ -274,8 +271,8 @@ public void testIdentifySubpackage() throws Exception { final PackageId arg1 = PackageId.fromString("my_packages:example:1.0"); final PackageId arg2 = PackageId.fromString("my_packages:other-example:1.0"); + argRecord.clear(); check.identifySubpackage(arg1, arg2); - Map.Entry call = argRecord.stream() .filter(entry -> "identifySubpackage".equals(entry.getKey()) && entry.getValue().length == 3).findFirst() .orElse(null); @@ -284,6 +281,32 @@ public void testIdentifySubpackage() throws Exception { assertSame("same arg2", arg2, call.getValue()[2]); } + @Test + public void testIdentifyEmbeddedPackage() throws Exception { + final Invocable delegate = mock(Invocable.class); + final ScriptProgressCheck.ScriptHelper helper = new ScriptProgressCheck.ScriptHelper(); + final ScriptProgressCheck check = new ScriptProgressCheck(delegate, helper, null); + + final List> argRecord = new ArrayList<>(); + doAnswer(call -> argRecord.add(toEntry(call.getArgument(0), call.getArguments()))) + .when(delegate).invokeFunction(anyString(), any()); + + final PackageId arg1 = PackageId.fromString("my_packages:example:1.0"); + final PackageId arg2 = PackageId.fromString("my_packages:other-example:1.0"); + final String arg3 = "/apps/mine/author-packages/install/" + arg1.getDownloadName(); + + argRecord.clear(); + check.identifyEmbeddedPackage(arg1, arg2, arg3); + Map.Entry call = argRecord.stream() + .filter(entry -> "identifyEmbeddedPackage".equals(entry.getKey()) && entry.getValue().length == 4).findFirst() + .orElse(null); + assertNotNull("expect call for identifyEmbeddedPackage", call); + assertSame("same arg1", arg1, call.getValue()[1]); + assertSame("same arg2", arg2, call.getValue()[2]); + assertSame("same arg3", arg3, call.getValue()[3]); + } + + @Test public void testReadManifest() throws Exception { final Invocable delegate = mock(Invocable.class); diff --git a/core/src/test/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulatorTest.java b/core/src/test/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulatorTest.java new file mode 100644 index 000000000..aa2048ad4 --- /dev/null +++ b/core/src/test/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulatorTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.core.sling; + +import net.adamcin.oakpal.core.OakpalPlan; +import org.apache.sling.installer.api.InstallableResource; +import org.apache.sling.installer.core.impl.InternalResource; +import org.junit.Test; + +import java.util.function.Function; + +import static net.adamcin.oakpal.api.JavaxJson.arr; +import static net.adamcin.oakpal.api.JavaxJson.key; +import static net.adamcin.oakpal.core.OakpalPlan.keys; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +public class DefaultSlingSimulatorTest { + + @Test + public void testReadInstallableResourceFromNode_noRepositoryException() throws Exception { + OakpalPlan.fromJson(key(keys().repoInits(), + arr().val("create path (nt:unstructured) /apps/config/Test")).get()) + .toOakMachineBuilder(null, getClass().getClassLoader()) + .build().initAndInspect(session -> { + assertNull("expect null config", + DefaultSlingSimulator.readInternalResourceFromNode(session.getNode("/apps/config/Test"))); + }); + } + + @Test + public void testReadInstallableResourceFromNode_slingOsgiConfig() throws Exception { + OakpalPlan.fromJson(key(keys().repoInits(), arr() + .val("register nodetypes") + .val("<<===") + .val("<'sling'='http://sling.apache.org/jcr/sling/1.0'>") + .val("[sling:OsgiConfig] > nt:unstructured, nt:hierarchyNode") + .val("===>>") + .val("create path (nt:folder) /apps/config/Test(sling:OsgiConfig)")) + .get()).toOakMachineBuilder(null, getClass().getClassLoader()) + .build().initAndInspect(session -> { + + InternalResource resource = DefaultSlingSimulator + .readInternalResourceFromNode(session.getNode("/apps/config/Test")).toOptional() + .flatMap(Function.identity()) + .orElse(null); + assertNotNull("expect not null resource", resource); + assertNotNull("expect not null config", resource.getDictionary()); + }); + } + + + + + +} \ No newline at end of file diff --git a/core/src/test/java/net/adamcin/oakpal/core/sling/EmbeddedPackageInstallableTest.java b/core/src/test/java/net/adamcin/oakpal/core/sling/EmbeddedPackageInstallableTest.java new file mode 100644 index 000000000..fd220b5bb --- /dev/null +++ b/core/src/test/java/net/adamcin/oakpal/core/sling/EmbeddedPackageInstallableTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.core.sling; + +import net.adamcin.oakpal.api.EmbeddedPackageInstallable; +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class EmbeddedPackageInstallableTest { + @Test + public void testConstructorAndGetters() { + final PackageId expectParentId = PackageId.fromString("test:test:1"); + final String expectJcrPath = "/some/path"; + final PackageId expectSubpackageId = PackageId.fromString("test:testtest:1"); + final EmbeddedPackageInstallable installable = + new EmbeddedPackageInstallable(expectParentId, expectJcrPath, expectSubpackageId); + + assertSame("expect parentId", expectParentId, installable.getParentId()); + assertSame("expect jcrPath", expectJcrPath, installable.getJcrPath()); + assertSame("expect subpackageId", expectSubpackageId, installable.getEmbeddedId()); + } +} \ No newline at end of file diff --git a/core/src/test/java/net/adamcin/oakpal/core/sling/NoopSlingSimulatorTest.java b/core/src/test/java/net/adamcin/oakpal/core/sling/NoopSlingSimulatorTest.java new file mode 100644 index 000000000..83449620f --- /dev/null +++ b/core/src/test/java/net/adamcin/oakpal/core/sling/NoopSlingSimulatorTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.core.sling; + +import net.adamcin.oakpal.api.EmbeddedPackageInstallable; +import net.adamcin.oakpal.api.RepoInitScriptsInstallable; +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.junit.Test; + +import java.util.Collections; + +import static net.adamcin.oakpal.core.sling.NoopSlingSimulator.instance; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +public class NoopSlingSimulatorTest { + + @Test + public void testInstance() { + assertNotNull("expect non-null instance", instance()); + } + + @Test + public void testDequeueInstallable() { + assertNull("never dequeue an installable", instance().dequeueInstallable()); + } + + @Test + public void testOpenRepoInitScripts() { + assertNotNull("expect non-null iterable", + instance().openRepoInitScripts( + new RepoInitScriptsInstallable(PackageId.fromString("test:pack:1"), "/test/path", + Collections.emptyList()))); + } + + @Test + public void testOpenEmbeddedPackage() { + assertNull("expect null package", + instance().openEmbeddedPackage( + new EmbeddedPackageInstallable(PackageId.fromString("test:pack:1"), "/test/path", + PackageId.fromString("test:pack:2")))); + } + +} \ No newline at end of file diff --git a/core/src/test/java/net/adamcin/oakpal/core/sling/RepoInitScriptsInstallableTest.java b/core/src/test/java/net/adamcin/oakpal/core/sling/RepoInitScriptsInstallableTest.java new file mode 100644 index 000000000..bab18be9d --- /dev/null +++ b/core/src/test/java/net/adamcin/oakpal/core/sling/RepoInitScriptsInstallableTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.core.sling; + +import net.adamcin.oakpal.api.RepoInitScriptsInstallable; +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.junit.Test; + +import java.util.Collections; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +public class RepoInitScriptsInstallableTest { + + @Test + public void testConstructorAndGetters() { + final PackageId expectParentId = PackageId.fromString("test:test:1"); + final String expectJcrPath = "/some/path"; + final RepoInitScriptsInstallable installable = new RepoInitScriptsInstallable(expectParentId, expectJcrPath, + Collections.emptyList(), null); + + assertSame("expect parentId", expectParentId, installable.getParentId()); + assertSame("expect jcrPath", expectJcrPath, installable.getJcrPath()); + assertNotNull("expect not null", installable.getScripts()); + assertNull("expect null convertedFrom", installable.getConvertedFrom()); + } +} \ No newline at end of file From dc075f7bf5d5302c3e3ee2ddda314d49bd552244 Mon Sep 17 00:00:00 2001 From: Mark Adamcin Date: Tue, 25 Aug 2020 14:27:23 -0700 Subject: [PATCH 02/10] initial revisit to get successful mvn clean install --- .../api/EmbeddedPackageInstallable.java | 3 +- .../oakpal/api/OsgiBundleInstallable.java | 83 ------------------- .../oakpal/api/OsgiConfigInstallable.java | 2 +- .../api/RepoInitScriptsInstallable.java | 13 +-- .../adamcin/oakpal/api/SlingInstallable.java | 23 +---- .../adamcin/oakpal/api/SlingSimulator.java | 6 +- .../net/adamcin/oakpal/core/OakMachine.java | 41 ++++----- .../oakpal/core/checks/SlingJcrInstaller.java | 1 + .../core/sling/DefaultSlingSimulator.java | 53 +++++------- .../oakpal/core/sling/NoopSlingSimulator.java | 17 +--- .../sling/OsgiBundleInstallableParams.java | 51 ------------ .../RepoInitScriptsInstallableParams.java | 38 +++++++++ .../core/sling/SlingInstallableParams.java | 2 +- .../core/sling/SlingSimulatorBackend.java | 25 ++---- .../adamcin/oakpal/core/OakMachineTest.java | 25 +++--- .../core/sling/DefaultSlingSimulatorTest.java | 14 ++-- .../core/sling/NoopSlingSimulatorTest.java | 20 ++--- .../sling/RepoInitScriptsInstallableTest.java | 4 +- 18 files changed, 134 insertions(+), 287 deletions(-) delete mode 100644 api/src/main/java/net/adamcin/oakpal/api/OsgiBundleInstallable.java delete mode 100644 core/src/main/java/net/adamcin/oakpal/core/sling/OsgiBundleInstallableParams.java create mode 100644 core/src/main/java/net/adamcin/oakpal/core/sling/RepoInitScriptsInstallableParams.java diff --git a/api/src/main/java/net/adamcin/oakpal/api/EmbeddedPackageInstallable.java b/api/src/main/java/net/adamcin/oakpal/api/EmbeddedPackageInstallable.java index 7230add18..54be1c446 100644 --- a/api/src/main/java/net/adamcin/oakpal/api/EmbeddedPackageInstallable.java +++ b/api/src/main/java/net/adamcin/oakpal/api/EmbeddedPackageInstallable.java @@ -16,6 +16,7 @@ package net.adamcin.oakpal.api; +import org.apache.jackrabbit.vault.packaging.JcrPackage; import org.apache.jackrabbit.vault.packaging.PackageId; import org.jetbrains.annotations.NotNull; @@ -24,7 +25,7 @@ /** * An installable path identified as an embedded package. */ -public final class EmbeddedPackageInstallable implements SlingInstallable { +public final class EmbeddedPackageInstallable implements SlingInstallable { private final @NotNull PackageId parentId; private final @NotNull String jcrPath; private final @NotNull PackageId embeddedId; diff --git a/api/src/main/java/net/adamcin/oakpal/api/OsgiBundleInstallable.java b/api/src/main/java/net/adamcin/oakpal/api/OsgiBundleInstallable.java deleted file mode 100644 index 58d6f040e..000000000 --- a/api/src/main/java/net/adamcin/oakpal/api/OsgiBundleInstallable.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2020 Mark Adamcin - * - * Licensed 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 net.adamcin.oakpal.api; - -import org.apache.jackrabbit.vault.packaging.PackageId; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.jar.Manifest; - -public final class OsgiBundleInstallable implements SlingInstallable { - private final @NotNull PackageId parentId; - private final @NotNull String jcrPath; - private final @Nullable String installationHint; - private final @NotNull Manifest manifest; - private final @NotNull String symbolicName; - private final @NotNull String version; - private final @Nullable String activationPolicy; - - public OsgiBundleInstallable(@NotNull final PackageId parentId, - @NotNull final String jcrPath, - @Nullable final String installationHint, - @NotNull final Manifest manifest, - @NotNull final String symbolicName, - @NotNull final String version, - @Nullable final String activationPolicy) { - this.parentId = parentId; - this.jcrPath = jcrPath; - this.installationHint = installationHint; - this.manifest = manifest; - this.symbolicName = symbolicName; - this.version = version; - this.activationPolicy = activationPolicy; - } - - @NotNull - @Override - public PackageId getParentId() { - return parentId; - } - - @NotNull - @Override - public String getJcrPath() { - return jcrPath; - } - - @Nullable - @Override - public String getInstallationHint() { - return installationHint; - } - - public Manifest getManifest() { - return (Manifest) manifest.clone(); - } - - public String getSymbolicName() { - return symbolicName; - } - - public String getVersion() { - return version; - } - - public String getActivationPolicy() { - return activationPolicy; - } -} diff --git a/api/src/main/java/net/adamcin/oakpal/api/OsgiConfigInstallable.java b/api/src/main/java/net/adamcin/oakpal/api/OsgiConfigInstallable.java index 9bf2e21b7..fdce686b0 100644 --- a/api/src/main/java/net/adamcin/oakpal/api/OsgiConfigInstallable.java +++ b/api/src/main/java/net/adamcin/oakpal/api/OsgiConfigInstallable.java @@ -27,7 +27,7 @@ /** * Sling Installable representing an OSGi config node. */ -public final class OsgiConfigInstallable implements SlingInstallable { +public final class OsgiConfigInstallable implements SlingInstallable> { private final @NotNull PackageId parentId; private final @NotNull String jcrPath; private final @NotNull Map properties; diff --git a/api/src/main/java/net/adamcin/oakpal/api/RepoInitScriptsInstallable.java b/api/src/main/java/net/adamcin/oakpal/api/RepoInitScriptsInstallable.java index 85e0978c1..8f30c2eb8 100644 --- a/api/src/main/java/net/adamcin/oakpal/api/RepoInitScriptsInstallable.java +++ b/api/src/main/java/net/adamcin/oakpal/api/RepoInitScriptsInstallable.java @@ -18,27 +18,23 @@ import org.apache.jackrabbit.vault.packaging.PackageId; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.List; /** * Locates a jcr path that should be treated as an installable provider of repoinit scripts. */ -public final class RepoInitScriptsInstallable implements SlingInstallable { +public final class RepoInitScriptsInstallable implements SlingInstallable> { private final @NotNull PackageId parentId; private final @NotNull String jcrPath; private final @NotNull List scripts; - private final @Nullable OsgiConfigInstallable convertedFrom; public RepoInitScriptsInstallable(final @NotNull PackageId parentId, final @NotNull String jcrPath, - final @NotNull List scripts, - final @Nullable OsgiConfigInstallable convertedFrom) { + final @NotNull List scripts) { this.parentId = parentId; this.jcrPath = jcrPath; this.scripts = scripts; - this.convertedFrom = convertedFrom; } @Override @@ -56,9 +52,4 @@ public List getScripts() { return scripts; } - @Nullable - @Override - public OsgiConfigInstallable getConvertedFrom() { - return convertedFrom; - } } diff --git a/api/src/main/java/net/adamcin/oakpal/api/SlingInstallable.java b/api/src/main/java/net/adamcin/oakpal/api/SlingInstallable.java index d0fd31b30..3992353ce 100644 --- a/api/src/main/java/net/adamcin/oakpal/api/SlingInstallable.java +++ b/api/src/main/java/net/adamcin/oakpal/api/SlingInstallable.java @@ -18,14 +18,15 @@ import org.apache.jackrabbit.vault.packaging.PackageId; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.osgi.annotation.versioning.ProviderType; /** * Type representing a Sling-installable resource JCR path. + * + * @param the concrete installable type */ @ProviderType -public interface SlingInstallable { +public interface SlingInstallable { /** * PackageId of the package that imported the resource. @@ -41,22 +42,4 @@ public interface SlingInstallable { */ @NotNull String getJcrPath(); - /** - * Get the installation hint if available. This would be the name of the parent node for osgi bundles. - * - * @return the osgi installation hint - */ - default @Nullable String getInstallationHint() { - return null; - } - - /** - * An installable might be converted from a generic form to a narrower form with special significance to Oakpal. - * This method provides a link to the original form, if any. - * - * @return the installable from which this was converted, or null - */ - default @Nullable SlingInstallable getConvertedFrom() { - return null; - } } diff --git a/api/src/main/java/net/adamcin/oakpal/api/SlingSimulator.java b/api/src/main/java/net/adamcin/oakpal/api/SlingSimulator.java index f237cb55e..a8059ba6e 100644 --- a/api/src/main/java/net/adamcin/oakpal/api/SlingSimulator.java +++ b/api/src/main/java/net/adamcin/oakpal/api/SlingSimulator.java @@ -40,8 +40,8 @@ public interface SlingSimulator { * @param node the JCR node of the embedded JCR package * @return a handle for the installable path or null */ - @Nullable SlingInstallable prepareInstallableNode(@NotNull PackageId parentPackageId, - @NotNull Node node); + @Nullable SlingInstallable prepareInstallableNode(@NotNull PackageId parentPackageId, + @NotNull Node node); /** * Submit a resource path for installation as a list of raw repoinit scripts. The best real-world example at this @@ -52,5 +52,5 @@ public interface SlingSimulator { * @param installable the installable * @return a handle for the installable path or null */ - @Nullable SlingInstallable submitInstallable(@NotNull SlingInstallable installable); + @Nullable SlingInstallable submitInstallable(@NotNull SlingInstallable installable); } 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 fdd1bd925..0c74781e2 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/OakMachine.java +++ b/core/src/main/java/net/adamcin/oakpal/core/OakMachine.java @@ -16,14 +16,14 @@ package net.adamcin.oakpal.core; +import net.adamcin.oakpal.api.EmbeddedPackageInstallable; import net.adamcin.oakpal.api.Fun; import net.adamcin.oakpal.api.PathAction; import net.adamcin.oakpal.api.ProgressCheck; +import net.adamcin.oakpal.api.RepoInitScriptsInstallable; import net.adamcin.oakpal.api.SilenceableCheck; import net.adamcin.oakpal.api.SlingInstallable; import net.adamcin.oakpal.core.sling.DefaultSlingSimulator; -import net.adamcin.oakpal.api.EmbeddedPackageInstallable; -import net.adamcin.oakpal.api.RepoInitScriptsInstallable; import net.adamcin.oakpal.core.sling.SlingSimulatorBackend; import org.apache.jackrabbit.JcrConstants; import org.apache.jackrabbit.api.JackrabbitRepository; @@ -918,15 +918,13 @@ final void processEmbeddedPackage(final @NotNull Session admin, final boolean preInstall) throws RepositoryException { final Consumer onError = error -> getErrorListener().onSlingEmbeddedPackageError(error, installable); - Fun.ThrowingSupplier supplier = slingSimulator.openEmbeddedPackage(installable); - if (supplier != null) { - internalProcessSubpackage(admin, manager, installable.getEmbeddedId(), preInstall, supplier, - check -> check.identifyEmbeddedPackage( - installable.getEmbeddedId(), - installable.getParentId(), - installable.getJcrPath()), - onError); - } + Fun.ThrowingSupplier supplier = slingSimulator.open(installable); + internalProcessSubpackage(admin, manager, installable.getEmbeddedId(), preInstall, supplier, + check -> check.identifyEmbeddedPackage( + installable.getEmbeddedId(), + installable.getParentId(), + installable.getJcrPath()), + onError); } private void processUploadedPackage(final @NotNull Session admin, @@ -951,20 +949,23 @@ void processInstallableQueue(final @NotNull Session admin, final @NotNull PackageId lastPackageId, final boolean preInstall) throws RepositoryException { final Session inspectSession = Util.wrapSessionReadOnly(admin); - SlingInstallable dequeued = slingSimulator.dequeueInstallable(); + SlingInstallable dequeued = slingSimulator.dequeueInstallable(); while (dequeued != null) { - final SlingInstallable installable = dequeued; + final SlingInstallable installable = dequeued; propagateCheckPackageEvent(preInstall, installable.getParentId(), check -> check.beforeSlingInstall(lastPackageId, installable, inspectSession)); if (installable instanceof RepoInitScriptsInstallable) { - for (final String repoInitScript : - slingSimulator.openRepoInitScripts((RepoInitScriptsInstallable) installable)) { - try (Reader reader = new StringReader(repoInitScript)) { - repoInitProcessor.apply(admin, reader); - } catch (final Exception e) { - getErrorListener().onSlingRepoInitScriptsError(e, repoInitScript, - (RepoInitScriptsInstallable) installable); + try { + for (final String repoInitScript : slingSimulator.open((RepoInitScriptsInstallable) installable).tryGet()) { + try (Reader reader = new StringReader(repoInitScript)) { + repoInitProcessor.apply(admin, reader); + } catch (final Exception e) { + getErrorListener().onSlingRepoInitScriptsError(e, repoInitScript, + (RepoInitScriptsInstallable) installable); + } } + } catch (Exception e) { + getErrorListener().onSlingRepoInitScriptsError(e, null, (RepoInitScriptsInstallable) installable); } } else if (installable instanceof EmbeddedPackageInstallable) { processEmbeddedPackage(admin, manager, (EmbeddedPackageInstallable) installable, preInstall); diff --git a/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java b/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java index a1c85fd41..5f614a441 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java +++ b/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java @@ -95,6 +95,7 @@ public void importedPath(final PackageId packageId, final String path, final Nod } + } } 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 ba040b89e..d0d580f0f 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 @@ -29,11 +29,9 @@ import org.apache.jackrabbit.vault.packaging.VaultPackage; import org.apache.jackrabbit.vault.packaging.impl.ZipVaultPackage; import org.apache.sling.installer.api.InstallableResource; -import org.apache.sling.installer.core.impl.FileDataStore; import org.apache.sling.installer.core.impl.InternalResource; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.osgi.framework.BundleContext; import javax.jcr.Node; import javax.jcr.Property; @@ -42,11 +40,8 @@ import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; -import java.io.File; import java.io.InputStream; import java.lang.reflect.Array; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Dictionary; import java.util.Enumeration; @@ -75,7 +70,7 @@ public static SlingSimulatorBackend instance() { private JcrPackageManager packageManager; private ErrorListener errorListener; - private final Queue installables = new LinkedList<>(); + private final Queue> installables = new LinkedList<>(); @Override public void startedScan() { @@ -98,25 +93,39 @@ public void setErrorListener(final ErrorListener errorListener) { } @Override - public @Nullable SlingInstallable dequeueInstallable() { + public @Nullable SlingInstallable dequeueInstallable() { return installables.poll(); } + @SuppressWarnings("unchecked") @Override + public @NotNull Fun.ThrowingSupplier + open(@NotNull final SlingInstallable installable) { + if (installable instanceof RepoInitScriptsInstallable) { + return () -> + (InstallableType) openRepoInitScripts((RepoInitScriptsInstallable) installable); + } else if (installable instanceof EmbeddedPackageInstallable) { + return () -> + (InstallableType) openEmbeddedPackage((EmbeddedPackageInstallable) installable); + } + return () -> { + throw new IllegalArgumentException("Unsupported installable type: " + installable.getClass()); + }; + } + public @NotNull Iterable openRepoInitScripts(@NotNull final RepoInitScriptsInstallable installable) { return installable.getScripts(); } - @Override public @Nullable Fun.ThrowingSupplier openEmbeddedPackage(@NotNull final EmbeddedPackageInstallable installable) { return () -> packageManager.open(session.getNode(installable.getJcrPath()), true); } @Override - public @Nullable SlingInstallable prepareInstallableNode(final @NotNull PackageId parentPackageId, - final @NotNull Node node) { + public @Nullable SlingInstallable prepareInstallableNode(final @NotNull PackageId parentPackageId, + final @NotNull Node node) { final Result jcrPathResult = result0(node::getPath).get(); final Result>> result = jcrPathResult .flatMap(result1(session::getNode)) @@ -130,7 +139,7 @@ public void setErrorListener(final ErrorListener errorListener) { } @Override - public @Nullable SlingInstallable submitInstallable(final @NotNull SlingInstallable installable) { + public @Nullable SlingInstallable submitInstallable(final @NotNull SlingInstallable installable) { installables.add(installable); return installable; } @@ -175,10 +184,7 @@ public void setErrorListener(final ErrorListener errorListener) { if (installable != null) { return Optional.of(installable); } - installable = maybeBundleResource(resource); - if (installable != null) { - return Optional.of(installable); - } + // maybe do something with bundles in the future } else if (InstallableResource.TYPE_PROPERTIES.equals(resource.getType())) { // convert to OsgiConfigInstallable installable = maybeConfigResource(resource); @@ -196,23 +202,6 @@ public void setErrorListener(final ErrorListener errorListener) { .map(EmbeddedPackageInstallableParams::new).toOptional().orElse(null); } - static void setInternalDataStore(final @NotNull File dataDir) { - final BundleContext bundleContext = - (BundleContext) Proxy.newProxyInstance(DefaultSlingSimulator.class.getClassLoader(), - new Class[]{BundleContext.class}, - (Object proxy, Method method, Object[] args) -> { - if ("getDataFile".equals(method.getName())) { - return dataDir; - } - return null; - }); - new FileDataStore(bundleContext); - } - - static @Nullable OsgiBundleInstallableParams maybeBundleResource(final @NotNull InternalResource resource) { - return new OsgiBundleInstallableParams(); - } - static String separatorsToUnix(final String path) { if (path == null || path.indexOf('\\') == -1) { return path; diff --git a/core/src/main/java/net/adamcin/oakpal/core/sling/NoopSlingSimulator.java b/core/src/main/java/net/adamcin/oakpal/core/sling/NoopSlingSimulator.java index 3bff85a06..06f11b80f 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/sling/NoopSlingSimulator.java +++ b/core/src/main/java/net/adamcin/oakpal/core/sling/NoopSlingSimulator.java @@ -16,13 +16,10 @@ package net.adamcin.oakpal.core.sling; -import net.adamcin.oakpal.api.EmbeddedPackageInstallable; import net.adamcin.oakpal.api.Fun; -import net.adamcin.oakpal.api.RepoInitScriptsInstallable; import net.adamcin.oakpal.api.SlingInstallable; import net.adamcin.oakpal.api.SlingSimulator; import net.adamcin.oakpal.core.ErrorListener; -import org.apache.jackrabbit.vault.packaging.JcrPackage; import org.apache.jackrabbit.vault.packaging.JcrPackageManager; import org.apache.jackrabbit.vault.packaging.PackageId; import org.jetbrains.annotations.NotNull; @@ -30,7 +27,6 @@ import javax.jcr.Node; import javax.jcr.Session; -import java.util.Collections; /** * Noop implementation of a SlingSimulator. @@ -61,15 +57,10 @@ public void setErrorListener(final ErrorListener errorListener) { } @Override - public @NotNull Iterable - openRepoInitScripts(@NotNull final RepoInitScriptsInstallable installable) { - return Collections.emptyList(); - } - - @Override - public @Nullable Fun.ThrowingSupplier - openEmbeddedPackage(@NotNull final EmbeddedPackageInstallable installable) { - return null; + public @NotNull Fun.ThrowingSupplier open(@NotNull final SlingInstallable installable) { + return () -> { + throw new IllegalStateException("Cannot install sling resources using using noop simulator."); + }; } @Override diff --git a/core/src/main/java/net/adamcin/oakpal/core/sling/OsgiBundleInstallableParams.java b/core/src/main/java/net/adamcin/oakpal/core/sling/OsgiBundleInstallableParams.java deleted file mode 100644 index 40d171145..000000000 --- a/core/src/main/java/net/adamcin/oakpal/core/sling/OsgiBundleInstallableParams.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2020 Mark Adamcin - * - * Licensed 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 net.adamcin.oakpal.core.sling; - -import net.adamcin.oakpal.api.OsgiBundleInstallable; -import org.apache.jackrabbit.vault.packaging.PackageId; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.jar.Manifest; - -public class OsgiBundleInstallableParams implements SlingInstallableParams { - private final @Nullable String installationHint; - private final @NotNull Manifest manifest; - private final @NotNull String symbolicName; - private final @NotNull String version; - private final @Nullable String activationPolicy; - - public OsgiBundleInstallableParams(@Nullable final String installationHint, - @NotNull final Manifest manifest, - @NotNull final String symbolicName, - @NotNull final String version, - @Nullable final String activationPolicy) { - this.installationHint = installationHint; - this.manifest = manifest; - this.symbolicName = symbolicName; - this.version = version; - this.activationPolicy = activationPolicy; - } - - @NotNull - @Override - public OsgiBundleInstallable createInstallable(final PackageId parentPackageId, final String jcrPath) { - return new OsgiBundleInstallable(parentPackageId, jcrPath, installationHint, - manifest, symbolicName, version, activationPolicy); - } -} diff --git a/core/src/main/java/net/adamcin/oakpal/core/sling/RepoInitScriptsInstallableParams.java b/core/src/main/java/net/adamcin/oakpal/core/sling/RepoInitScriptsInstallableParams.java new file mode 100644 index 000000000..0af64b495 --- /dev/null +++ b/core/src/main/java/net/adamcin/oakpal/core/sling/RepoInitScriptsInstallableParams.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.core.sling; + +import net.adamcin.oakpal.api.RepoInitScriptsInstallable; +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class RepoInitScriptsInstallableParams implements SlingInstallableParams { + + private final List scripts; + + public RepoInitScriptsInstallableParams(final List scripts) { + this.scripts = scripts; + } + + @NotNull + @Override + public RepoInitScriptsInstallable createInstallable(final PackageId parentPackageId, final String jcrPath) { + return new RepoInitScriptsInstallable(parentPackageId, jcrPath, scripts); + } +} diff --git a/core/src/main/java/net/adamcin/oakpal/core/sling/SlingInstallableParams.java b/core/src/main/java/net/adamcin/oakpal/core/sling/SlingInstallableParams.java index b64ac7a73..dd765b073 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/sling/SlingInstallableParams.java +++ b/core/src/main/java/net/adamcin/oakpal/core/sling/SlingInstallableParams.java @@ -26,6 +26,6 @@ * @param sling installable type that will be created */ @ProviderType -public interface SlingInstallableParams { +public interface SlingInstallableParams> { @NotNull T createInstallable(PackageId parentPackageId, String jcrPath); } diff --git a/core/src/main/java/net/adamcin/oakpal/core/sling/SlingSimulatorBackend.java b/core/src/main/java/net/adamcin/oakpal/core/sling/SlingSimulatorBackend.java index 07bd17342..7461a9906 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/sling/SlingSimulatorBackend.java +++ b/core/src/main/java/net/adamcin/oakpal/core/sling/SlingSimulatorBackend.java @@ -16,14 +16,11 @@ package net.adamcin.oakpal.core.sling; -import net.adamcin.oakpal.api.EmbeddedPackageInstallable; import net.adamcin.oakpal.api.Fun; -import net.adamcin.oakpal.api.RepoInitScriptsInstallable; import net.adamcin.oakpal.api.ScanListener; import net.adamcin.oakpal.api.SlingInstallable; import net.adamcin.oakpal.api.SlingSimulator; import net.adamcin.oakpal.core.ErrorListener; -import org.apache.jackrabbit.vault.packaging.JcrPackage; import org.apache.jackrabbit.vault.packaging.JcrPackageManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -66,23 +63,15 @@ public interface SlingSimulatorBackend extends SlingSimulator, ScanListener { * * @return a resource to be installed immediately or null to continue the scan */ - @Nullable SlingInstallable dequeueInstallable(); + @Nullable SlingInstallable dequeueInstallable(); /** - * Get installable, raw repoinit scripts from the specified JCR path. + * Get installable entities from the specified JCR path. * - * @param installable the installable - * @return an iterable of raw repoinit scripts + * @param installable the installable + * @param the concrete installable type + * @return an entity supplier from the provided JCR path */ - @NotNull Iterable - openRepoInitScripts(@NotNull RepoInitScriptsInstallable installable); - - /** - * Get installable JCR package from the specified JCR path. - * - * @param installable the subpackage installable - * @return a subpackage supplier or null - */ - @Nullable Fun.ThrowingSupplier - openEmbeddedPackage(@NotNull EmbeddedPackageInstallable installable); + @NotNull Fun.ThrowingSupplier + open(@NotNull SlingInstallable installable); } diff --git a/core/src/test/java/net/adamcin/oakpal/core/OakMachineTest.java b/core/src/test/java/net/adamcin/oakpal/core/OakMachineTest.java index 53648dc67..8a9564328 100644 --- a/core/src/test/java/net/adamcin/oakpal/core/OakMachineTest.java +++ b/core/src/test/java/net/adamcin/oakpal/core/OakMachineTest.java @@ -17,6 +17,7 @@ package net.adamcin.oakpal.core; import junitx.util.PrivateAccessor; +import net.adamcin.oakpal.api.Fun; import net.adamcin.oakpal.api.PathAction; import net.adamcin.oakpal.api.ProgressCheck; import net.adamcin.oakpal.api.SilenceableCheck; @@ -674,7 +675,7 @@ public void testProcessSubpackageInstallable_onSubpackageException() throws Exce any(EmbeddedPackageInstallable.class)); final SlingSimulatorBackend installWatcher = mock(SlingSimulatorBackend.class); - when(installWatcher.openEmbeddedPackage(installable)) + when(installWatcher.open(installable)) .thenReturn(() -> manager.open(sub1)); builder() .withSlingSimulator(installWatcher) @@ -706,7 +707,7 @@ public void testProcessSubpackageInstallable_bubbledRepositoryException() throws any(EmbeddedPackageInstallable.class)); final SlingSimulatorBackend installWatcher = mock(SlingSimulatorBackend.class); - when(installWatcher.openEmbeddedPackage(installable)) + when(installWatcher.open(installable)) .thenReturn(() -> manager.open(sub1)); builder() .withSlingSimulator(installWatcher) @@ -806,7 +807,7 @@ public void testProcessInstallableQueue_singleSubpackageInstallable() throws Exc doAnswer(call -> { openedSlot.complete(call.getArgument(0)); return null; - }).when(installWatcher).openEmbeddedPackage(installable); + }).when(installWatcher).open(installable); new OakMachine.Builder() .withSlingSimulator(installWatcher) @@ -841,18 +842,20 @@ public void testProcessInstallableQueue_singleRepoInitInstallable() throws Excep }).when(installWatcher).dequeueInstallable(); final CompletableFuture readerSlot = new CompletableFuture<>(); - final OakMachine.RepoInitProcessor repoInitProcessor = mock(OakMachine.RepoInitProcessor.class); - doAnswer(call -> readerSlot.complete(call.getArgument(1))) - .when(repoInitProcessor).apply(any(Session.class), any(Reader.class)); + final OakMachine.RepoInitProcessor repoInitProcessor = (Session sess, Reader read) -> { + readerSlot.complete(read); + }; final CompletableFuture openedSlot = new CompletableFuture<>(); doAnswer(call -> { openedSlot.complete(call.getArgument(0)); - List scripts = new ArrayList<>(); - scripts.add("create user testProcessInstallableQueue_singleRepoInitInstallable"); - scripts.add(null); - return scripts; - }).when(installWatcher).openRepoInitScripts(installable); + return (Fun.ThrowingSupplier>) () -> { + List scripts = new ArrayList<>(); + scripts.add("create user testProcessInstallableQueue_singleRepoInitInstallable"); + scripts.add(null); + return scripts; + }; + }).when(installWatcher).open(installable); final CompletableFuture eLatch = new CompletableFuture<>(); final CompletableFuture scriptLatch = new CompletableFuture<>(); diff --git a/core/src/test/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulatorTest.java b/core/src/test/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulatorTest.java index aa2048ad4..81000510f 100644 --- a/core/src/test/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulatorTest.java +++ b/core/src/test/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulatorTest.java @@ -16,18 +16,19 @@ package net.adamcin.oakpal.core.sling; +import net.adamcin.oakpal.api.Result; import net.adamcin.oakpal.core.OakpalPlan; -import org.apache.sling.installer.api.InstallableResource; import org.apache.sling.installer.core.impl.InternalResource; import org.junit.Test; +import java.util.Optional; import java.util.function.Function; import static net.adamcin.oakpal.api.JavaxJson.arr; import static net.adamcin.oakpal.api.JavaxJson.key; import static net.adamcin.oakpal.core.OakpalPlan.keys; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; public class DefaultSlingSimulatorTest { @@ -37,8 +38,10 @@ public void testReadInstallableResourceFromNode_noRepositoryException() throws E arr().val("create path (nt:unstructured) /apps/config/Test")).get()) .toOakMachineBuilder(null, getClass().getClassLoader()) .build().initAndInspect(session -> { - assertNull("expect null config", - DefaultSlingSimulator.readInternalResourceFromNode(session.getNode("/apps/config/Test"))); + + Result> result = DefaultSlingSimulator + .readInternalResourceFromNode(session.getNode("/apps/config/Test")); + assertTrue("expect null config", result.isSuccess() && !result.getOrDefault(null).isPresent()); }); } @@ -64,7 +67,4 @@ public void testReadInstallableResourceFromNode_slingOsgiConfig() throws Excepti } - - - } \ No newline at end of file diff --git a/core/src/test/java/net/adamcin/oakpal/core/sling/NoopSlingSimulatorTest.java b/core/src/test/java/net/adamcin/oakpal/core/sling/NoopSlingSimulatorTest.java index 83449620f..134c89257 100644 --- a/core/src/test/java/net/adamcin/oakpal/core/sling/NoopSlingSimulatorTest.java +++ b/core/src/test/java/net/adamcin/oakpal/core/sling/NoopSlingSimulatorTest.java @@ -39,20 +39,16 @@ public void testDequeueInstallable() { assertNull("never dequeue an installable", instance().dequeueInstallable()); } - @Test - public void testOpenRepoInitScripts() { - assertNotNull("expect non-null iterable", - instance().openRepoInitScripts( - new RepoInitScriptsInstallable(PackageId.fromString("test:pack:1"), "/test/path", - Collections.emptyList()))); + @Test(expected = IllegalStateException.class) + public void testOpenRepoInitScripts() throws Exception { + instance().open(new RepoInitScriptsInstallable(PackageId.fromString("test:pack:1"), "/test/path", + Collections.emptyList())).tryGet(); } - @Test - public void testOpenEmbeddedPackage() { - assertNull("expect null package", - instance().openEmbeddedPackage( - new EmbeddedPackageInstallable(PackageId.fromString("test:pack:1"), "/test/path", - PackageId.fromString("test:pack:2")))); + @Test(expected = IllegalStateException.class) + public void testOpenEmbeddedPackage() throws Exception { + instance().open(new EmbeddedPackageInstallable(PackageId.fromString("test:pack:1"), "/test/path", + PackageId.fromString("test:pack:2"))).tryGet(); } } \ No newline at end of file diff --git a/core/src/test/java/net/adamcin/oakpal/core/sling/RepoInitScriptsInstallableTest.java b/core/src/test/java/net/adamcin/oakpal/core/sling/RepoInitScriptsInstallableTest.java index bab18be9d..a6814d975 100644 --- a/core/src/test/java/net/adamcin/oakpal/core/sling/RepoInitScriptsInstallableTest.java +++ b/core/src/test/java/net/adamcin/oakpal/core/sling/RepoInitScriptsInstallableTest.java @@ -23,7 +23,6 @@ import java.util.Collections; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; public class RepoInitScriptsInstallableTest { @@ -33,11 +32,10 @@ public void testConstructorAndGetters() { final PackageId expectParentId = PackageId.fromString("test:test:1"); final String expectJcrPath = "/some/path"; final RepoInitScriptsInstallable installable = new RepoInitScriptsInstallable(expectParentId, expectJcrPath, - Collections.emptyList(), null); + Collections.emptyList()); assertSame("expect parentId", expectParentId, installable.getParentId()); assertSame("expect jcrPath", expectJcrPath, installable.getJcrPath()); assertNotNull("expect not null", installable.getScripts()); - assertNull("expect null convertedFrom", installable.getConvertedFrom()); } } \ No newline at end of file From 8eafd2c02636d4268ac8f29635e9a2b782e6b701 Mon Sep 17 00:00:00 2001 From: Mark Adamcin Date: Tue, 25 Aug 2020 14:40:31 -0700 Subject: [PATCH 03/10] rebased and reversioned --- api/pom.xml | 2 +- cli/pom.xml | 2 +- core/pom.xml | 3 +-- maven/pom.xml | 2 +- pom.xml | 10 +++++----- testing/pom.xml | 2 +- webster/pom.xml | 2 +- 7 files changed, 11 insertions(+), 12 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index 5a9cc7dd9..484751399 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -21,7 +21,7 @@ net.adamcin.oakpal oakpal - 2.1.1-SNAPSHOT + 2.2.0-SNAPSHOT .. diff --git a/cli/pom.xml b/cli/pom.xml index f4768eb26..3b6a0a34f 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -21,7 +21,7 @@ net.adamcin.oakpal oakpal - 2.1.1-SNAPSHOT + 2.2.0-SNAPSHOT .. diff --git a/core/pom.xml b/core/pom.xml index 134a528da..56243bd1b 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,8 +21,7 @@ net.adamcin.oakpal oakpal - 2.1.1-SNAPSHOT - .. + 2.2.0-SNAPSHOT oakpal-core diff --git a/maven/pom.xml b/maven/pom.xml index a890327e7..27d820f97 100644 --- a/maven/pom.xml +++ b/maven/pom.xml @@ -21,7 +21,7 @@ net.adamcin.oakpal oakpal - 2.1.1-SNAPSHOT + 2.2.0-SNAPSHOT .. diff --git a/pom.xml b/pom.xml index d93fa62b3..2e7adf459 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ net.adamcin.oakpal oakpal - 2.1.1-SNAPSHOT + 2.2.0-SNAPSHOT pom 2017 @@ -498,22 +498,22 @@ net.adamcin.oakpal oakpal-testing - 2.1.1-SNAPSHOT + 2.2.0-SNAPSHOT net.adamcin.oakpal oakpal-api - 2.1.1-SNAPSHOT + 2.2.0-SNAPSHOT net.adamcin.oakpal oakpal-core - 2.1.1-SNAPSHOT + 2.2.0-SNAPSHOT net.adamcin.oakpal oakpal-webster - 2.1.1-SNAPSHOT + 2.2.0-SNAPSHOT org.jetbrains diff --git a/testing/pom.xml b/testing/pom.xml index 37e358fa5..2c28b89d5 100644 --- a/testing/pom.xml +++ b/testing/pom.xml @@ -21,7 +21,7 @@ net.adamcin.oakpal oakpal - 2.1.1-SNAPSHOT + 2.2.0-SNAPSHOT .. diff --git a/webster/pom.xml b/webster/pom.xml index ee2d61f90..0052c3f8c 100644 --- a/webster/pom.xml +++ b/webster/pom.xml @@ -21,7 +21,7 @@ net.adamcin.oakpal oakpal - 2.1.1-SNAPSHOT + 2.2.0-SNAPSHOT .. From 3e7338b9e8a101ba8eef95f198abe35ff30efa16 Mon Sep 17 00:00:00 2001 From: Mark Adamcin Date: Wed, 26 Aug 2020 08:26:06 -0700 Subject: [PATCH 04/10] Cleanup and incorporation of jedelson suggestions --- .../net/adamcin/oakpal/api/ProgressCheck.java | 4 +- .../adamcin/oakpal/api/SlingSimulator.java | 16 +- core/pom.xml | 3 +- .../net/adamcin/oakpal/core/OakpalPlan.java | 4 + .../oakpal/core/checks/SlingJcrInstaller.java | 32 ++- .../core/sling/DefaultSlingSimulator.java | 214 ++++++++++++------ .../oakpal/core/sling/NoopSlingSimulator.java | 11 +- .../sling/OsgiConfigInstallableParams.java | 12 + .../RepoInitScriptsInstallableParams.java | 18 ++ .../core/checks/SlingJcrInstallerTest.java} | 9 +- .../core/sling/DefaultSlingSimulatorTest.java | 17 +- 11 files changed, 237 insertions(+), 103 deletions(-) rename core/src/{main/java/net/adamcin/oakpal/core/sling/InternalResource.java => test/java/net/adamcin/oakpal/core/checks/SlingJcrInstallerTest.java} (83%) diff --git a/api/src/main/java/net/adamcin/oakpal/api/ProgressCheck.java b/api/src/main/java/net/adamcin/oakpal/api/ProgressCheck.java index 22bc4231b..fc9e483fc 100644 --- a/api/src/main/java/net/adamcin/oakpal/api/ProgressCheck.java +++ b/api/src/main/java/net/adamcin/oakpal/api/ProgressCheck.java @@ -202,7 +202,7 @@ default void identifyEmbeddedPackage(PackageId packageId, PackageId parentId, St * @throws RepositoryException because of access to a {@link Session} * @since 2.1.0 */ - default void beforeSlingInstall(PackageId lastPackage, SlingInstallable slingInstallable, Session inspectSession) + default void beforeSlingInstall(PackageId lastPackage, SlingInstallable slingInstallable, Session inspectSession) throws RepositoryException { } @@ -217,7 +217,7 @@ default void beforeSlingInstall(PackageId lastPackage, SlingInstallable slingIns * @throws RepositoryException because of access to a {@link Session} * @since 2.1.0 */ - default void appliedRepoInitScripts(PackageId lastPackage, SlingInstallable slingInstallable, Session inspectSession) + default void appliedRepoInitScripts(PackageId lastPackage, SlingInstallable slingInstallable, Session inspectSession) throws RepositoryException { } diff --git a/api/src/main/java/net/adamcin/oakpal/api/SlingSimulator.java b/api/src/main/java/net/adamcin/oakpal/api/SlingSimulator.java index a8059ba6e..6e5e8fda6 100644 --- a/api/src/main/java/net/adamcin/oakpal/api/SlingSimulator.java +++ b/api/src/main/java/net/adamcin/oakpal/api/SlingSimulator.java @@ -30,8 +30,9 @@ public interface SlingSimulator { /** - * Submit resource path for installation as an embedded FileVault package, generally located under {@code /apps/}. - * This should not be used to request traditional installation of {@code subpackages} under {@code /etc/packages}. + * Submit resource path for installation as an embedded FileVault package or other Sling installable, generally + * located under {@code /apps/}. This should not be used to request traditional installation of {@code subpackages} + * under {@code /etc/packages}. *

* Ideally, this method should be called by a check during * {@link ProgressCheck#importedPath(PackageId, String, Node, PathAction)}. @@ -42,15 +43,4 @@ public interface SlingSimulator { */ @Nullable SlingInstallable prepareInstallableNode(@NotNull PackageId parentPackageId, @NotNull Node node); - - /** - * Submit a resource path for installation as a list of raw repoinit scripts. The best real-world example at this - * time are - * May be called by a check during - * {@link ProgressCheck#importedPath(PackageId, String, Node, PathAction)}. - * - * @param installable the installable - * @return a handle for the installable path or null - */ - @Nullable SlingInstallable submitInstallable(@NotNull SlingInstallable installable); } diff --git a/core/pom.xml b/core/pom.xml index 56243bd1b..f1b2855ea 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -90,6 +90,7 @@ Private-Package: net.adamcin.oakpal.core.jcrfacade.* Import-Package: !aQute.*,\ !org.apache.sling.jcr.repoinit.*,\ + !org.apache.felix.cm.file,\ * ]]> @@ -158,8 +159,8 @@ org.apache.sling:org.apache.sling.installer.core + org/apache/felix/cm/file/ConfigurationHandler* org/apache/sling/installer/api/InstallableResource* - org/apache/sling/installer/core/impl/InternalResource* diff --git a/core/src/main/java/net/adamcin/oakpal/core/OakpalPlan.java b/core/src/main/java/net/adamcin/oakpal/core/OakpalPlan.java index 8468e6a00..14be07768 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/OakpalPlan.java +++ b/core/src/main/java/net/adamcin/oakpal/core/OakpalPlan.java @@ -417,6 +417,9 @@ private static OakpalPlan fromJson(final @NotNull Builder builder, if (hasNonNull(json, keys().repoInits())) { builder.withRepoInits(JavaxJson.mapArrayOfStrings(json.getJsonArray(keys().repoInits()))); } + if (hasNonNull(json, keys().runModes())) { + builder.withRunModes(JavaxJson.mapArrayOfStrings(json.getJsonArray(keys().runModes()))); + } if (hasNonNull(json, keys().forcedRoots())) { builder.withForcedRoots(JavaxJson.mapArrayOfObjects(json.getJsonArray(keys().forcedRoots()), ForcedRoot::fromJson)); @@ -502,6 +505,7 @@ public Builder startingWithPlan(final @NotNull OakpalPlan plan) { .withInstallHookPolicy(plan.getInstallHookPolicy()) .withRepoInitUrls(plan.getRepoInitUrls()) .withRepoInits(plan.getRepoInits()) + .withRunModes(plan.getRunModes()) .withPreInstallUrls(plan.getPreInstallUrls()); } diff --git a/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java b/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java index 5f614a441..56597de25 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java +++ b/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java @@ -16,11 +16,14 @@ package net.adamcin.oakpal.core.checks; +import net.adamcin.oakpal.api.JavaxJson; import net.adamcin.oakpal.api.PathAction; import net.adamcin.oakpal.api.ProgressCheck; import net.adamcin.oakpal.api.ProgressCheckFactory; +import net.adamcin.oakpal.api.Severity; import net.adamcin.oakpal.api.SimpleProgressCheckFactoryCheck; import net.adamcin.oakpal.api.SlingSimulator; +import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter; import org.apache.jackrabbit.vault.packaging.PackageId; import org.jetbrains.annotations.NotNull; import org.osgi.annotation.versioning.ProviderType; @@ -28,14 +31,24 @@ import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.json.JsonObject; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.regex.Pattern; +/** + * The {@code SlingJcrInstaller} is a check implementation that watches for Sling installable nodes and registers them + * with the {@link SlingSimulator} when sling simulation is active. + *

+ * {@code config} options: + *

+ *
{@code rootPaths} (Default: {@code [/apps, /libs]})
+ *
The root paths that the listener watches for install and config nodes.
+ *
+ */ public final class SlingJcrInstaller implements ProgressCheckFactory { static final String DEFAULT_INSTALL_PATH_PATTERN = "^(/[^/]*)*/(install|config)$"; + static final List DEFAULT_ROOT_PATHS = Arrays.asList("/apps", "/libs"); @ProviderType public interface JsonKeys { @@ -55,7 +68,9 @@ public static JsonKeys keys() { @Override public ProgressCheck newInstance(final JsonObject config) throws Exception { - return new Check(Arrays.asList("/apps", "/libs")); + final List rootPaths = JavaxJson.optArray(config, keys().rootPaths()).map(JavaxJson::mapArrayOfStrings) + .orElse(DEFAULT_ROOT_PATHS); + return new Check(rootPaths); } static final class Check extends SimpleProgressCheckFactoryCheck { @@ -64,8 +79,6 @@ static final class Check extends SimpleProgressCheckFactoryCheck parentPaths = new ArrayList<>(); - Check(final @NotNull List rootPaths) { super(SlingJcrInstaller.class); this.rootPaths = rootPaths; @@ -74,12 +87,12 @@ static final class Check extends SimpleProgressCheckFactoryCheck runModes) { this.slingSimulator = slingSimulator; + installPattern = compileInstallPattern(runModes); } Pattern compileInstallPattern(final @NotNull Set runModes) { @@ -94,8 +107,17 @@ public void importedPath(final PackageId packageId, final String path, final Nod return; } + if (path.startsWith("/etc/packages/")) { + return; + } + if (rootPaths.stream().noneMatch(path::startsWith)) { + return; + } + if (installPattern.matcher(node.getParent().getPath()).matches()) { + this.slingSimulator.prepareInstallableNode(packageId, node); + } } } 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 d0d580f0f..664060e40 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 @@ -23,13 +23,12 @@ import net.adamcin.oakpal.api.SlingInstallable; import net.adamcin.oakpal.api.SlingSimulator; import net.adamcin.oakpal.core.ErrorListener; +import org.apache.felix.cm.file.ConfigurationHandler; import org.apache.jackrabbit.vault.packaging.JcrPackage; import org.apache.jackrabbit.vault.packaging.JcrPackageManager; import org.apache.jackrabbit.vault.packaging.PackageId; import org.apache.jackrabbit.vault.packaging.VaultPackage; -import org.apache.jackrabbit.vault.packaging.impl.ZipVaultPackage; import org.apache.sling.installer.api.InstallableResource; -import org.apache.sling.installer.core.impl.InternalResource; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -40,21 +39,23 @@ import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; +import java.io.BufferedInputStream; +import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Array; import java.util.Arrays; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; -import java.util.Hashtable; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; 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.compose1; import static net.adamcin.oakpal.api.Fun.result0; import static net.adamcin.oakpal.api.Fun.result1; @@ -129,18 +130,15 @@ public void setErrorListener(final ErrorListener errorListener) { final Result jcrPathResult = result0(node::getPath).get(); final Result>> result = jcrPathResult .flatMap(result1(session::getNode)) - .flatMap(DefaultSlingSimulator::readInternalResourceFromNode) - .map(resource -> resource.flatMap(DefaultSlingSimulator::createSlingInstallableParams)); - return jcrPathResult.flatMap(jcrPath -> + .flatMap(this::readInstallableParamsFromNode); + SlingInstallable installable = jcrPathResult.flatMap(jcrPath -> result.map(optParams -> optParams.map(params -> params.createInstallable(parentPackageId, jcrPath)))) .toOptional().flatMap(Function.identity()) .orElse(null); - } - - @Override - public @Nullable SlingInstallable submitInstallable(final @NotNull SlingInstallable installable) { - installables.add(installable); + if (installable != null) { + installables.add(installable); + } return installable; } @@ -148,58 +146,85 @@ public void setErrorListener(final ErrorListener errorListener) { static final String SLING_OSGI_CONFIG = "{" + SLING_NS + "}OsgiConfig"; static final String JCR_CONTENT_DATA = "jcr:content/jcr:data"; - static @NotNull Result> - readInternalResourceFromNode(final @NotNull Node node) { + static class NodeRes { + private final Node node; + private final String path; + private final Map props = new HashMap<>(); + + public NodeRes(final Node node, final String path) { + this.node = node; + this.path = path; + } + + public Node getNode() { + return node; + } + + public String getPath() { + return path; + } + + public Map getProps() { + return props; + } + } + + @NotNull Result>> + readInstallableParamsFromNode(final @NotNull Node node) { return result0(() -> { final String path = node.getPath(); + NodeRes nodeRes = new NodeRes(node, path); if (Arrays.asList(node.getSession().getWorkspace().getNamespaceRegistry().getURIs()).contains(SLING_NS) && node.isNodeType(SLING_OSGI_CONFIG)) { - final Dictionary props = new Hashtable<>(); - loadJcrProperties(props, node); - return Optional.of(new InstallableResource(path, null, props, "", null, 20)); + // handle sling:OsgiConfig + loadJcrProperties(nodeRes.getProps(), node); + + OsgiConfigInstallableParams configInstallableParams = maybeConfigResource(nodeRes); + if (configInstallableParams != null) { + return Optional.>ofNullable(RepoInitScriptsInstallableParams + .fromOsgiConfigInstallableParams(configInstallableParams)) + .orElse(configInstallableParams); + } } else if (node.hasProperty(JCR_CONTENT_DATA)) { - final InputStream is = node.getProperty(JCR_CONTENT_DATA).getStream(); - final Dictionary dict = new Hashtable<>(); - dict.put(InstallableResource.INSTALLATION_HINT, node.getParent().getName()); - return Optional.of(new InstallableResource(path, is, dict, "", null, 200)); - } - return Optional.empty(); - }).get().flatMap(resource -> resource - .map(compose1( - DefaultSlingSimulator::readInternalResourceFromInstallableResource, - result -> result.map(Optional::of))) - .orElse(Result.success(Optional.empty()))); - } + // this could be a properties file, or a package file (.zip), or a bundle (.jar) + // check extension here + nodeRes.getProps().put(InstallableResource.INSTALLATION_HINT, node.getParent().getName()); - static @NotNull Result - readInternalResourceFromInstallableResource(final @NotNull InstallableResource resource) { - return result0(() -> InternalResource.create("jcrinstall", resource)).get(); - } + EmbeddedPackageInstallableParams packageInstallableParams = maybePackageResource(nodeRes); + if (packageInstallableParams != null) { + return packageInstallableParams; + } - static @NotNull Optional> - createSlingInstallableParams(final @NotNull InternalResource resource) { - SlingInstallableParams installable = null; - if (InstallableResource.TYPE_FILE.equals(resource.getType())) { - installable = maybePackageResource(resource); - if (installable != null) { - return Optional.of(installable); - } - // maybe do something with bundles in the future - } else if (InstallableResource.TYPE_PROPERTIES.equals(resource.getType())) { - // convert to OsgiConfigInstallable - installable = maybeConfigResource(resource); - if (installable != null) { - // if RepoInit, wrap with RepoInitScriptInstallable - return Optional.of(installable); + if (isConfigExtension(path)) { + try (InputStream is = node.getProperty(JCR_CONTENT_DATA).getBinary().getStream()) { + nodeRes.getProps().putAll(readDictionary(is, nodeRes.getPath())); + } + + OsgiConfigInstallableParams configInstallableParams = maybeConfigResource(nodeRes); + if (configInstallableParams != null) { + return Optional.>ofNullable(RepoInitScriptsInstallableParams + .fromOsgiConfigInstallableParams(configInstallableParams)) + .orElse(configInstallableParams); + } + } } - } - return Optional.empty(); + return null; + }).get().map(Optional::ofNullable); } - static @Nullable EmbeddedPackageInstallableParams maybePackageResource(final @NotNull InternalResource resource) { - return result0(() -> new ZipVaultPackage(resource.getPrivateCopyOfFile(), true, false)).get() - .map(VaultPackage::getId) - .map(EmbeddedPackageInstallableParams::new).toOptional().orElse(null); + @Nullable EmbeddedPackageInstallableParams maybePackageResource(final @NotNull NodeRes nodeRes) { + if (nodeRes.getPath().endsWith(".zip")) { + try (JcrPackage pack = packageManager.open(nodeRes.getNode(), true)) { + return result1(JcrPackage::getPackage).apply(pack) + .flatMap(result1(VaultPackage::getId)) + .map(EmbeddedPackageInstallableParams::new) + .toOptional().orElse(null); + } catch (RepositoryException e) { + /* TODO log this or something? */ + return null; + } + } + return null; } static String separatorsToUnix(final String path) { @@ -235,8 +260,73 @@ private static String removeConfigExtension(final String id) { return id; } - static @Nullable OsgiConfigInstallableParams maybeConfigResource(final @NotNull InternalResource resource) { - final String lastIdPart = getResourceId(resource.getURL()); + private static boolean isConfigExtension(String url) { + for (final String ext : EXTENSIONS) { + if (url.endsWith(ext)) { + return true; + } + } + return false; + } + + /** + * Read dictionary from an input stream. + * We use the same logic as Apache Felix FileInstall here, but only for .config files: + * *.config files are handled by the Apache Felix ConfigAdmin file reader + * + * @param is the input stream + * @param id the id + * @throws IOException + */ + static Map readDictionary(final InputStream is, final String id) + throws IOException { + final Map ht = new LinkedHashMap<>(); + + try (final BufferedInputStream in = new BufferedInputStream(is)) { + + if (id.endsWith(".config")) { + // check for initial comment line + in.mark(256); + final int firstChar = in.read(); + if (firstChar == '#') { + int b; + while ((b = in.read()) != '\n') { + if (b == -1) { + throw new IOException("Unable to read configuration."); + } + } + } else { + in.reset(); + } + @SuppressWarnings("unchecked") final Dictionary config = ConfigurationHandler.read(in); + final Enumeration i = config.keys(); + while (i.hasMoreElements()) { + final String key = i.nextElement(); + ht.put(key, config.get(key)); + } + } else { + final Properties p = new Properties(); + in.mark(1); + boolean isXml = in.read() == '<'; + in.reset(); + if (isXml) { + p.loadFromXML(in); + } else { + p.load(in); + } + final Enumeration i = p.keys(); + while (i.hasMoreElements()) { + final Object key = i.nextElement(); + ht.put(key.toString(), p.get(key)); + } + } + } + return ht; + } + + + static @Nullable OsgiConfigInstallableParams maybeConfigResource(final @NotNull NodeRes resource) { + final String lastIdPart = getResourceId(resource.getPath()); // remove extension if known final String pid = removeConfigExtension(lastIdPart); @@ -256,18 +346,10 @@ private static String removeConfigExtension(final String id) { configPid = pid; } - final Map properties = new HashMap<>(); - Optional.ofNullable(resource.getPrivateCopyOfDictionary()).ifPresent(dict -> { - for (final Enumeration keys = dict.keys(); keys.hasMoreElements(); ) { - final String key = keys.nextElement(); - properties.put(key, dict.get(key)); - } - }); - - return new OsgiConfigInstallableParams(properties, configPid, factoryPid); + return new OsgiConfigInstallableParams(resource.getProps(), configPid, factoryPid); } - static void loadJcrProperties(final @NotNull Dictionary configMap, final @NotNull Node configNode) + static void loadJcrProperties(final @NotNull Map configMap, final @NotNull Node configNode) throws RepositoryException { final PropertyIterator pi = configNode.getProperties(); while (pi.hasNext()) { diff --git a/core/src/main/java/net/adamcin/oakpal/core/sling/NoopSlingSimulator.java b/core/src/main/java/net/adamcin/oakpal/core/sling/NoopSlingSimulator.java index 06f11b80f..28ee5957c 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/sling/NoopSlingSimulator.java +++ b/core/src/main/java/net/adamcin/oakpal/core/sling/NoopSlingSimulator.java @@ -52,7 +52,7 @@ public void setErrorListener(final ErrorListener errorListener) { } @Override - public @Nullable SlingInstallable dequeueInstallable() { + public @Nullable SlingInstallable dequeueInstallable() { return null; } @@ -64,13 +64,8 @@ public void setErrorListener(final ErrorListener errorListener) { } @Override - public @Nullable SlingInstallable prepareInstallableNode(final @NotNull PackageId parentPackageId, - final @NotNull Node node) { - return null; - } - - @Override - public @Nullable SlingInstallable submitInstallable(final @NotNull SlingInstallable installable) { + public @Nullable SlingInstallable prepareInstallableNode(final @NotNull PackageId parentPackageId, + final @NotNull Node node) { return null; } diff --git a/core/src/main/java/net/adamcin/oakpal/core/sling/OsgiConfigInstallableParams.java b/core/src/main/java/net/adamcin/oakpal/core/sling/OsgiConfigInstallableParams.java index bd32aa64e..106baa23d 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/sling/OsgiConfigInstallableParams.java +++ b/core/src/main/java/net/adamcin/oakpal/core/sling/OsgiConfigInstallableParams.java @@ -38,6 +38,18 @@ public OsgiConfigInstallableParams(@NotNull final Map properties this.factoryPid = factoryPid; } + public String getServicePid() { + return servicePid; + } + + public String getFactoryPid() { + return factoryPid; + } + + public Map getProperties() { + return properties; + } + @NotNull @Override public OsgiConfigInstallable createInstallable(final PackageId parentPackageId, final String jcrPath) { diff --git a/core/src/main/java/net/adamcin/oakpal/core/sling/RepoInitScriptsInstallableParams.java b/core/src/main/java/net/adamcin/oakpal/core/sling/RepoInitScriptsInstallableParams.java index 0af64b495..4e72bedfa 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/sling/RepoInitScriptsInstallableParams.java +++ b/core/src/main/java/net/adamcin/oakpal/core/sling/RepoInitScriptsInstallableParams.java @@ -17,12 +17,17 @@ package net.adamcin.oakpal.core.sling; import net.adamcin.oakpal.api.RepoInitScriptsInstallable; +import org.apache.jackrabbit.oak.commons.PropertiesUtil; import org.apache.jackrabbit.vault.packaging.PackageId; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.Arrays; import java.util.List; public class RepoInitScriptsInstallableParams implements SlingInstallableParams { + public static final String REPO_INIT_FACTORY_PID = "org.apache.sling.jcr.repoinit.RepositoryInitializer"; + public static final String CONFIG_SCRIPTS = "scripts"; private final List scripts; @@ -30,9 +35,22 @@ public RepoInitScriptsInstallableParams(final List scripts) { this.scripts = scripts; } + public List getScripts() { + return scripts; + } + @NotNull @Override public RepoInitScriptsInstallable createInstallable(final PackageId parentPackageId, final String jcrPath) { return new RepoInitScriptsInstallable(parentPackageId, jcrPath, scripts); } + + @Nullable + static RepoInitScriptsInstallableParams fromOsgiConfigInstallableParams(final @NotNull OsgiConfigInstallableParams params) { + if (REPO_INIT_FACTORY_PID.equals(params.getFactoryPid()) && params.getProperties().containsKey(CONFIG_SCRIPTS)) { + return new RepoInitScriptsInstallableParams(Arrays.asList(PropertiesUtil + .toStringArray(params.getProperties().get(CONFIG_SCRIPTS), new String[0]))); + } + return null; + } } diff --git a/core/src/main/java/net/adamcin/oakpal/core/sling/InternalResource.java b/core/src/test/java/net/adamcin/oakpal/core/checks/SlingJcrInstallerTest.java similarity index 83% rename from core/src/main/java/net/adamcin/oakpal/core/sling/InternalResource.java rename to core/src/test/java/net/adamcin/oakpal/core/checks/SlingJcrInstallerTest.java index f2d48cecb..3e659f21c 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/sling/InternalResource.java +++ b/core/src/test/java/net/adamcin/oakpal/core/checks/SlingJcrInstallerTest.java @@ -14,7 +14,10 @@ * limitations under the License. */ -package net.adamcin.oakpal.core.sling; +package net.adamcin.oakpal.core.checks; -public class InternalResource { -} +import static org.junit.Assert.*; + +public class SlingJcrInstallerTest { + +} \ No newline at end of file diff --git a/core/src/test/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulatorTest.java b/core/src/test/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulatorTest.java index 81000510f..d01381a96 100644 --- a/core/src/test/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulatorTest.java +++ b/core/src/test/java/net/adamcin/oakpal/core/sling/DefaultSlingSimulatorTest.java @@ -27,20 +27,23 @@ import static net.adamcin.oakpal.api.JavaxJson.arr; import static net.adamcin.oakpal.api.JavaxJson.key; import static net.adamcin.oakpal.core.OakpalPlan.keys; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class DefaultSlingSimulatorTest { + private DefaultSlingSimulator slingSimulator = new DefaultSlingSimulator(); @Test public void testReadInstallableResourceFromNode_noRepositoryException() throws Exception { + OakpalPlan.fromJson(key(keys().repoInits(), arr().val("create path (nt:unstructured) /apps/config/Test")).get()) .toOakMachineBuilder(null, getClass().getClassLoader()) .build().initAndInspect(session -> { - Result> result = DefaultSlingSimulator - .readInternalResourceFromNode(session.getNode("/apps/config/Test")); + Result>> result = slingSimulator + .readInstallableParamsFromNode(session.getNode("/apps/config/Test")); assertTrue("expect null config", result.isSuccess() && !result.getOrDefault(null).isPresent()); }); } @@ -57,12 +60,16 @@ public void testReadInstallableResourceFromNode_slingOsgiConfig() throws Excepti .get()).toOakMachineBuilder(null, getClass().getClassLoader()) .build().initAndInspect(session -> { - InternalResource resource = DefaultSlingSimulator - .readInternalResourceFromNode(session.getNode("/apps/config/Test")).toOptional() + SlingInstallableParams resource = slingSimulator + .readInstallableParamsFromNode(session.getNode("/apps/config/Test")).toOptional() .flatMap(Function.identity()) .orElse(null); assertNotNull("expect not null resource", resource); - assertNotNull("expect not null config", resource.getDictionary()); + assertTrue("expect instance of OsgiConfigInstallableParams", + resource instanceof OsgiConfigInstallableParams); + OsgiConfigInstallableParams params = (OsgiConfigInstallableParams) resource; + assertNotNull("expect not null properties", params.getProperties()); + assertEquals("expect servicePid is Test", "Test", params.getServicePid()); }); } From a7f833cba46d6bcbcaff95f85ae22a0e2091bbc7 Mon Sep 17 00:00:00 2001 From: Mark Adamcin Date: Wed, 26 Aug 2020 08:49:56 -0700 Subject: [PATCH 05/10] correct the script handler events --- .../net/adamcin/oakpal/core/ScriptProgressCheck.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/net/adamcin/oakpal/core/ScriptProgressCheck.java b/core/src/main/java/net/adamcin/oakpal/core/ScriptProgressCheck.java index 8a31560af..118551b51 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/ScriptProgressCheck.java +++ b/core/src/main/java/net/adamcin/oakpal/core/ScriptProgressCheck.java @@ -88,7 +88,7 @@ *
{@link ProgressCheck#beforeSlingInstall(PackageId, SlingInstallable, Session)}
*
identifyEmbeddedPackage(packageId, parentPackageId, jcrPath)
*
{@link ProgressCheck#identifyEmbeddedPackage(PackageId, PackageId, String)}
- *
afterSlingInstall(packageId, slingInstallable, inspectSession)
+ *
appliedRepoInitScripts(packageId, slingInstallable, inspectSession)
*
{@link ProgressCheck#appliedRepoInitScripts(PackageId, SlingInstallable, Session)}
*
afterScanPackage(packageId, inspectSession)
*
{@link ProgressCheck#afterScanPackage(PackageId, Session)}
@@ -114,7 +114,7 @@ public final class ScriptProgressCheck implements ProgressCheck { public static final String INVOKE_ON_IDENTIFY_SUBPACKAGE = "identifySubpackage"; public static final String INVOKE_ON_BEFORE_SLING_INSTALL = "beforeSlingInstall"; public static final String INVOKE_ON_IDENTIFY_EMBEDDED_PACKAGE = "identifyEmbeddedPackage"; - public static final String INVOKE_ON_AFTER_SLING_INSTALL = "afterSlingInstall"; + public static final String INVOKE_ON_APPLIED_REPO_INIT_SCRIPTS = "appliedRepoInitScripts"; public static final String INVOKE_ON_AFTER_SCAN_PACKAGE = "afterScanPackage"; public static final String INVOKE_ON_FINISHED_SCAN = "finishedScan"; public static final String INVOKE_GET_CHECK_NAME = "getCheckName"; @@ -295,7 +295,7 @@ public void identifySubpackage(final PackageId packageId, final PackageId parent @Override public void beforeSlingInstall(final PackageId lastPackage, - final SlingInstallable slingInstallable, + final SlingInstallable slingInstallable, final Session inspectSession) throws RepositoryException { guardSessionHandler(INVOKE_ON_BEFORE_SLING_INSTALL, handle -> handle.apply(lastPackage, slingInstallable, inspectSession)); @@ -310,9 +310,9 @@ public void identifyEmbeddedPackage(final PackageId packageId, @Override public void appliedRepoInitScripts(final PackageId lastPackage, - final SlingInstallable slingInstallable, + final SlingInstallable slingInstallable, final Session inspectSession) throws RepositoryException { - guardSessionHandler(INVOKE_ON_AFTER_SLING_INSTALL, + guardSessionHandler(INVOKE_ON_APPLIED_REPO_INIT_SCRIPTS, handle -> handle.apply(lastPackage, slingInstallable, inspectSession)); } From af8f6f056d78bd4a666cd1332f4d94acad4d5876 Mon Sep 17 00:00:00 2001 From: Justin Edelson Date: Wed, 26 Aug 2020 13:52:20 -0400 Subject: [PATCH 06/10] minor JCR install fixes --- .../net/adamcin/oakpal/core/checks/SlingJcrInstaller.java | 2 +- .../adamcin/oakpal/core/sling/DefaultSlingSimulator.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java b/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java index 56597de25..4b6546534 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java +++ b/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java @@ -67,7 +67,7 @@ public static JsonKeys keys() { } @Override - public ProgressCheck newInstance(final JsonObject config) throws Exception { + public ProgressCheck newInstance(final JsonObject config) { final List rootPaths = JavaxJson.optArray(config, keys().rootPaths()).map(JavaxJson::mapArrayOfStrings) .orElse(DEFAULT_ROOT_PATHS); return new Check(rootPaths); 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 664060e40..62ecf9144 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 @@ -119,9 +119,9 @@ public void setErrorListener(final ErrorListener errorListener) { return installable.getScripts(); } - public @Nullable Fun.ThrowingSupplier - openEmbeddedPackage(@NotNull final EmbeddedPackageInstallable installable) { - return () -> packageManager.open(session.getNode(installable.getJcrPath()), true); + public @Nullable JcrPackage + openEmbeddedPackage(@NotNull final EmbeddedPackageInstallable installable) throws RepositoryException { + return packageManager.open(session.getNode(installable.getJcrPath()), true); } @Override From c18f45c292680f1cafc2e7641dc91cc8c5ef958c Mon Sep 17 00:00:00 2001 From: Justin Edelson Date: Wed, 26 Aug 2020 16:52:18 -0400 Subject: [PATCH 07/10] embedded packages have to be uploaded, not opened also add error handling in DefaultErrorListener --- .../oakpal/core/DefaultErrorListener.java | 18 ++++++++++++++++++ .../core/sling/DefaultSlingSimulator.java | 9 ++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/net/adamcin/oakpal/core/DefaultErrorListener.java b/core/src/main/java/net/adamcin/oakpal/core/DefaultErrorListener.java index fb2d5cbdf..4b1d9cff8 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/DefaultErrorListener.java +++ b/core/src/main/java/net/adamcin/oakpal/core/DefaultErrorListener.java @@ -16,7 +16,9 @@ package net.adamcin.oakpal.core; +import net.adamcin.oakpal.api.EmbeddedPackageInstallable; import net.adamcin.oakpal.api.ProgressCheck; +import net.adamcin.oakpal.api.RepoInitScriptsInstallable; import net.adamcin.oakpal.api.ReportCollector; import net.adamcin.oakpal.api.Severity; import net.adamcin.oakpal.api.SimpleViolation; @@ -213,4 +215,20 @@ public void onRepoInitInlineError(final Throwable error, final List repo LOGGER.trace("[onRepoInitInlineError] stack trace for: " + message, error); reportViolation(new SimpleViolation(Severity.MAJOR, message)); } + + @Override + public void onSlingEmbeddedPackageError(Throwable error, EmbeddedPackageInstallable installable) { + final String message = MessageFormat.format(getString("embedded package error ({0}:{1}): {2} \"{3}\""), + installable.getParentId(), installable.getJcrPath(), error.getClass().getName(), error.getMessage()); + LOGGER.trace("[onSlingEmbeddedPackageError] stack trace for: " + message, error); + reportViolation(new SimpleViolation(Severity.MAJOR, message)); + } + + @Override + public void onSlingRepoInitScriptsError(Throwable error, String failedScript, RepoInitScriptsInstallable installable) { + final String message = MessageFormat.format(getString("embedded repoinit script error ({0}:{1}): {2} \"{3}\""), + installable.getParentId(), installable.getJcrPath(), error.getClass().getName(), error.getMessage()); + LOGGER.trace("[onSlingRepoInitScriptsError] stack trace for: " + message, error); + reportViolation(new SimpleViolation(Severity.MAJOR, message)); + } } 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 62ecf9144..6cc094494 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 @@ -24,6 +24,7 @@ import net.adamcin.oakpal.api.SlingSimulator; import net.adamcin.oakpal.core.ErrorListener; import org.apache.felix.cm.file.ConfigurationHandler; +import org.apache.jackrabbit.commons.JcrUtils; import org.apache.jackrabbit.vault.packaging.JcrPackage; import org.apache.jackrabbit.vault.packaging.JcrPackageManager; import org.apache.jackrabbit.vault.packaging.PackageId; @@ -121,7 +122,13 @@ public void setErrorListener(final ErrorListener errorListener) { public @Nullable JcrPackage openEmbeddedPackage(@NotNull final EmbeddedPackageInstallable installable) throws RepositoryException { - return packageManager.open(session.getNode(installable.getJcrPath()), true); + Node packageNode = session.getNode(installable.getJcrPath()); + try (InputStream input = JcrUtils.readFile(packageNode)) { + return packageManager.upload(input, true, true); + } catch (IOException e) { + errorListener.onSlingEmbeddedPackageError(e, installable); + return null; + } } @Override From b332e86b894a8fecd8ca48165149406a37cf2b3e Mon Sep 17 00:00:00 2001 From: Justin Edelson Date: Wed, 26 Aug 2020 17:48:35 -0400 Subject: [PATCH 08/10] fix test failure in IntelliJ --- .../src/test/java/net/adamcin/oakpal/core/OakMachineTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/net/adamcin/oakpal/core/OakMachineTest.java b/core/src/test/java/net/adamcin/oakpal/core/OakMachineTest.java index 8a9564328..974959280 100644 --- a/core/src/test/java/net/adamcin/oakpal/core/OakMachineTest.java +++ b/core/src/test/java/net/adamcin/oakpal/core/OakMachineTest.java @@ -40,6 +40,7 @@ import org.apache.jackrabbit.vault.fs.io.Archive; import org.apache.jackrabbit.vault.packaging.InstallContext; import org.apache.jackrabbit.vault.packaging.InstallHookProcessor; +import org.apache.jackrabbit.vault.packaging.JcrPackage; import org.apache.jackrabbit.vault.packaging.JcrPackageManager; import org.apache.jackrabbit.vault.packaging.PackageException; import org.apache.jackrabbit.vault.packaging.PackageId; @@ -803,10 +804,11 @@ public void testProcessInstallableQueue_singleSubpackageInstallable() throws Exc } }).when(installWatcher).dequeueInstallable(); + final JcrPackage jcrPackage = mock(JcrPackage.class); final CompletableFuture openedSlot = new CompletableFuture<>(); doAnswer(call -> { openedSlot.complete(call.getArgument(0)); - return null; + return (Fun.ThrowingSupplier) () -> jcrPackage; }).when(installWatcher).open(installable); new OakMachine.Builder() From 1f19e07ee25b58a92ea5bd24ce85bf8dcd0d9b43 Mon Sep 17 00:00:00 2001 From: Mark Adamcin Date: Thu, 27 Aug 2020 18:40:43 -0700 Subject: [PATCH 09/10] add caliper packages and GrandTourIT to core --- calipers/README.md | 5 + calipers/all/pom.xml | 110 +++++++++++++++++ .../main/content/META-INF/vault/filter.xml | 20 +++ calipers/pom.xml | 66 ++++++++++ calipers/ui.apps.author/pom.xml | 82 +++++++++++++ .../main/content/META-INF/vault/filter.xml | 20 +++ .../src/main/content/jcr_root/.gitkeep | 0 .../main/content/jcr_root/apps/.content.xml | 20 +++ .../apps/oakpal-caliper-author/.content.xml | 20 +++ calipers/ui.apps.publish/pom.xml | 82 +++++++++++++ .../main/content/META-INF/vault/filter.xml | 20 +++ .../src/main/content/jcr_root/.gitkeep | 0 .../main/content/jcr_root/apps/.content.xml | 20 +++ .../apps/oakpal-caliper-publish/.content.xml | 20 +++ calipers/ui.apps.structure/pom.xml | 68 +++++++++++ calipers/ui.apps/pom.xml | 82 +++++++++++++ .../main/content/META-INF/vault/filter.xml | 20 +++ .../src/main/content/jcr_root/.gitkeep | 0 .../main/content/jcr_root/apps/.content.xml | 20 +++ .../jcr_root/apps/oakpal-caliper/.content.xml | 20 +++ ...repoinit.RepositoryInitializer~init.config | 3 + calipers/ui.content.suba2/pom.xml | 78 ++++++++++++ .../main/content/META-INF/vault/filter.xml | 20 +++ .../content/oakpal-caliper/.content.xml | 20 +++ calipers/ui.content.subb3/pom.xml | 78 ++++++++++++ .../main/content/META-INF/vault/filter.xml | 20 +++ .../content/oakpal-caliper/.content.xml | 20 +++ calipers/ui.content.subc1/pom.xml | 78 ++++++++++++ .../main/content/META-INF/vault/filter.xml | 20 +++ .../content/oakpal-caliper/.content.xml | 20 +++ calipers/ui.content/pom.xml | 114 ++++++++++++++++++ .../main/content/META-INF/vault/filter.xml | 20 +++ .../content/oakpal-caliper/.content.xml | 20 +++ core/pom.xml | 9 ++ .../oakpal/core/checks/SlingJcrInstaller.java | 2 +- .../net/adamcin/oakpal/it/GrandTourIT.java | 109 +++++++++++++++++ pom.xml | 27 +++++ testing/pom.xml | 9 ++ .../oakpal/testing/TestPackageUtil.java | 4 + .../oakpal/testing/TestPackageUtilTest.java | 6 + 40 files changed, 1371 insertions(+), 1 deletion(-) create mode 100644 calipers/README.md create mode 100644 calipers/all/pom.xml create mode 100644 calipers/all/src/main/content/META-INF/vault/filter.xml create mode 100644 calipers/pom.xml create mode 100644 calipers/ui.apps.author/pom.xml create mode 100644 calipers/ui.apps.author/src/main/content/META-INF/vault/filter.xml create mode 100644 calipers/ui.apps.author/src/main/content/jcr_root/.gitkeep create mode 100644 calipers/ui.apps.author/src/main/content/jcr_root/apps/.content.xml create mode 100644 calipers/ui.apps.author/src/main/content/jcr_root/apps/oakpal-caliper-author/.content.xml create mode 100644 calipers/ui.apps.publish/pom.xml create mode 100644 calipers/ui.apps.publish/src/main/content/META-INF/vault/filter.xml create mode 100644 calipers/ui.apps.publish/src/main/content/jcr_root/.gitkeep create mode 100644 calipers/ui.apps.publish/src/main/content/jcr_root/apps/.content.xml create mode 100644 calipers/ui.apps.publish/src/main/content/jcr_root/apps/oakpal-caliper-publish/.content.xml create mode 100644 calipers/ui.apps.structure/pom.xml create mode 100644 calipers/ui.apps/pom.xml create mode 100644 calipers/ui.apps/src/main/content/META-INF/vault/filter.xml create mode 100644 calipers/ui.apps/src/main/content/jcr_root/.gitkeep create mode 100644 calipers/ui.apps/src/main/content/jcr_root/apps/.content.xml create mode 100644 calipers/ui.apps/src/main/content/jcr_root/apps/oakpal-caliper/.content.xml create mode 100644 calipers/ui.apps/src/main/content/jcr_root/apps/oakpal-caliper/config/org.apache.sling.jcr.repoinit.RepositoryInitializer~init.config create mode 100644 calipers/ui.content.suba2/pom.xml create mode 100644 calipers/ui.content.suba2/src/main/content/META-INF/vault/filter.xml create mode 100644 calipers/ui.content.suba2/src/main/content/jcr_root/content/oakpal-caliper/.content.xml create mode 100644 calipers/ui.content.subb3/pom.xml create mode 100644 calipers/ui.content.subb3/src/main/content/META-INF/vault/filter.xml create mode 100644 calipers/ui.content.subb3/src/main/content/jcr_root/content/oakpal-caliper/.content.xml create mode 100644 calipers/ui.content.subc1/pom.xml create mode 100644 calipers/ui.content.subc1/src/main/content/META-INF/vault/filter.xml create mode 100644 calipers/ui.content.subc1/src/main/content/jcr_root/content/oakpal-caliper/.content.xml create mode 100644 calipers/ui.content/pom.xml create mode 100644 calipers/ui.content/src/main/content/META-INF/vault/filter.xml create mode 100644 calipers/ui.content/src/main/content/jcr_root/content/oakpal-caliper/.content.xml create mode 100644 core/src/test/java/net/adamcin/oakpal/it/GrandTourIT.java diff --git a/calipers/README.md b/calipers/README.md new file mode 100644 index 000000000..f36efc745 --- /dev/null +++ b/calipers/README.md @@ -0,0 +1,5 @@ +# OakPAL Caliper + +The OakPAL Caliper is a content package that exercises all the features of the OakMachine and events handled by the +ProgressCheck interface. It is published as an artifact in its own right so that it can be used as a standalone +self-test for a configured instance of the CLI or Maven Plugin. \ No newline at end of file diff --git a/calipers/all/pom.xml b/calipers/all/pom.xml new file mode 100644 index 000000000..489219694 --- /dev/null +++ b/calipers/all/pom.xml @@ -0,0 +1,110 @@ + + + + + 4.0.0 + + + net.adamcin.oakpal + oakpal-calipers + 2.2.0-SNAPSHOT + + + oakpal-caliper.all + content-package + + OakPAL - Calipers - all package + OakPAL Caliper - well-formed all package containing ui.apps and ui.content packages. + + 2017 + + + https://github.com/adamcin/oakpal + scm:git:git@github.com:adamcin/oakpal.git + scm:git://github.com/adamcin/oakpal.git + HEAD + + + + + + + + + org.apache.jackrabbit + filevault-package-maven-plugin + true + + net.adamcin.oakpal + mixed + + + net.adamcin.oakpal + oakpal-caliper.ui.apps + zip + /apps/oakpal-caliper-packages/application/install + + + net.adamcin.oakpal + oakpal-caliper.ui.apps.author + zip + /apps/oakpal-caliper-packages/application/install.author + + + net.adamcin.oakpal + oakpal-caliper.ui.apps.publish + zip + /apps/oakpal-caliper-packages/application/install.publish + + + net.adamcin.oakpal + oakpal-caliper.ui.content + zip + /apps/oakpal-caliper-packages/content/install + + + + + + + + + net.adamcin.oakpal + oakpal-caliper.ui.apps + 2.2.0-SNAPSHOT + zip + + + net.adamcin.oakpal + oakpal-caliper.ui.apps.author + 2.2.0-SNAPSHOT + zip + + + net.adamcin.oakpal + oakpal-caliper.ui.apps.publish + 2.2.0-SNAPSHOT + zip + + + net.adamcin.oakpal + oakpal-caliper.ui.content + 2.2.0-SNAPSHOT + zip + + + diff --git a/calipers/all/src/main/content/META-INF/vault/filter.xml b/calipers/all/src/main/content/META-INF/vault/filter.xml new file mode 100644 index 000000000..ea5da68b0 --- /dev/null +++ b/calipers/all/src/main/content/META-INF/vault/filter.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/calipers/pom.xml b/calipers/pom.xml new file mode 100644 index 000000000..6407a73be --- /dev/null +++ b/calipers/pom.xml @@ -0,0 +1,66 @@ + + + + + 4.0.0 + + + net.adamcin.oakpal + oakpal + 2.2.0-SNAPSHOT + + + oakpal-calipers + pom + + OakPAL - Calipers - Parent + Parent project for OakPAL Caliper Packages + + 2017 + + + https://github.com/adamcin/oakpal + scm:git:git@github.com:adamcin/oakpal.git + scm:git://github.com/adamcin/oakpal.git + HEAD + + + + ui.apps.structure + ui.apps + ui.apps.author + ui.apps.publish + ui.content + ui.content.suba2 + ui.content.subb3 + ui.content.subc1 + all + + + + + + + org.apache.jackrabbit + filevault-package-maven-plugin + 1.1.4 + + + + + diff --git a/calipers/ui.apps.author/pom.xml b/calipers/ui.apps.author/pom.xml new file mode 100644 index 000000000..5aabaed0a --- /dev/null +++ b/calipers/ui.apps.author/pom.xml @@ -0,0 +1,82 @@ + + + + + 4.0.0 + + + net.adamcin.oakpal + oakpal-calipers + 2.2.0-SNAPSHOT + + + oakpal-caliper.ui.apps.author + content-package + + OakPAL - Calipers - ui.apps.author + OakPAL Caliper - well-formed ui.apps.author package delivering content for author run mode only. + + 2017 + + + https://github.com/adamcin/oakpal + scm:git:git@github.com:adamcin/oakpal.git + scm:git://github.com/adamcin/oakpal.git + HEAD + + + + + + + src/main/content/jcr_root + + + + + + org.apache.jackrabbit + filevault-package-maven-plugin + true + + net.adamcin.oakpal + oakpal-caliper.ui.apps.author + application + merge + + none + + + + net.adamcin.oakpal + oakpal-caliper.ui.apps.structure + + + + + + + + + net.adamcin.oakpal + oakpal-caliper.ui.apps.structure + 2.2.0-SNAPSHOT + zip + + + diff --git a/calipers/ui.apps.author/src/main/content/META-INF/vault/filter.xml b/calipers/ui.apps.author/src/main/content/META-INF/vault/filter.xml new file mode 100644 index 000000000..ab0ceec72 --- /dev/null +++ b/calipers/ui.apps.author/src/main/content/META-INF/vault/filter.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/calipers/ui.apps.author/src/main/content/jcr_root/.gitkeep b/calipers/ui.apps.author/src/main/content/jcr_root/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/calipers/ui.apps.author/src/main/content/jcr_root/apps/.content.xml b/calipers/ui.apps.author/src/main/content/jcr_root/apps/.content.xml new file mode 100644 index 000000000..fe3716e8c --- /dev/null +++ b/calipers/ui.apps.author/src/main/content/jcr_root/apps/.content.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/calipers/ui.apps.author/src/main/content/jcr_root/apps/oakpal-caliper-author/.content.xml b/calipers/ui.apps.author/src/main/content/jcr_root/apps/oakpal-caliper-author/.content.xml new file mode 100644 index 000000000..fe3716e8c --- /dev/null +++ b/calipers/ui.apps.author/src/main/content/jcr_root/apps/oakpal-caliper-author/.content.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/calipers/ui.apps.publish/pom.xml b/calipers/ui.apps.publish/pom.xml new file mode 100644 index 000000000..008884bc9 --- /dev/null +++ b/calipers/ui.apps.publish/pom.xml @@ -0,0 +1,82 @@ + + + + + 4.0.0 + + + net.adamcin.oakpal + oakpal-calipers + 2.2.0-SNAPSHOT + + + oakpal-caliper.ui.apps.publish + content-package + + OakPAL - Calipers - ui.apps.publish + OakPAL Caliper - well-formed ui.apps package delivering content for publish run mode only. + + 2017 + + + https://github.com/adamcin/oakpal + scm:git:git@github.com:adamcin/oakpal.git + scm:git://github.com/adamcin/oakpal.git + HEAD + + + + + + + src/main/content/jcr_root + + + + + + org.apache.jackrabbit + filevault-package-maven-plugin + true + + net.adamcin.oakpal + oakpal-caliper.ui.apps.publish + application + merge + + none + + + + net.adamcin.oakpal + oakpal-caliper.ui.apps.structure + + + + + + + + + net.adamcin.oakpal + oakpal-caliper.ui.apps.structure + 2.2.0-SNAPSHOT + zip + + + diff --git a/calipers/ui.apps.publish/src/main/content/META-INF/vault/filter.xml b/calipers/ui.apps.publish/src/main/content/META-INF/vault/filter.xml new file mode 100644 index 000000000..2f1d6ddf3 --- /dev/null +++ b/calipers/ui.apps.publish/src/main/content/META-INF/vault/filter.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/calipers/ui.apps.publish/src/main/content/jcr_root/.gitkeep b/calipers/ui.apps.publish/src/main/content/jcr_root/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/calipers/ui.apps.publish/src/main/content/jcr_root/apps/.content.xml b/calipers/ui.apps.publish/src/main/content/jcr_root/apps/.content.xml new file mode 100644 index 000000000..fe3716e8c --- /dev/null +++ b/calipers/ui.apps.publish/src/main/content/jcr_root/apps/.content.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/calipers/ui.apps.publish/src/main/content/jcr_root/apps/oakpal-caliper-publish/.content.xml b/calipers/ui.apps.publish/src/main/content/jcr_root/apps/oakpal-caliper-publish/.content.xml new file mode 100644 index 000000000..fe3716e8c --- /dev/null +++ b/calipers/ui.apps.publish/src/main/content/jcr_root/apps/oakpal-caliper-publish/.content.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/calipers/ui.apps.structure/pom.xml b/calipers/ui.apps.structure/pom.xml new file mode 100644 index 000000000..f317545ba --- /dev/null +++ b/calipers/ui.apps.structure/pom.xml @@ -0,0 +1,68 @@ + + + + + 4.0.0 + + + net.adamcin.oakpal + oakpal-calipers + 2.2.0-SNAPSHOT + + + oakpal-caliper.ui.apps.structure + content-package + + OakPAL - Calipers - ui.apps repository structure + OakPAL Caliper - empty repository structure package for ui.apps filter. + + 2017 + + + https://github.com/adamcin/oakpal + scm:git:git@github.com:adamcin/oakpal.git + scm:git://github.com/adamcin/oakpal.git + HEAD + + + + + + + + + + + + org.apache.jackrabbit + filevault-package-maven-plugin + true + + + none + + + + + /apps + + + + + + diff --git a/calipers/ui.apps/pom.xml b/calipers/ui.apps/pom.xml new file mode 100644 index 000000000..121867182 --- /dev/null +++ b/calipers/ui.apps/pom.xml @@ -0,0 +1,82 @@ + + + + + 4.0.0 + + + net.adamcin.oakpal + oakpal-calipers + 2.2.0-SNAPSHOT + + + oakpal-caliper.ui.apps + content-package + + OakPAL - Calipers - ui.apps + OakPAL Caliper - well-formed ui.apps package containing /apps/ content. + + 2017 + + + https://github.com/adamcin/oakpal + scm:git:git@github.com:adamcin/oakpal.git + scm:git://github.com/adamcin/oakpal.git + HEAD + + + + + + + src/main/content/jcr_root + + + + + + org.apache.jackrabbit + filevault-package-maven-plugin + true + + net.adamcin.oakpal + oakpal-caliper.ui.apps + mixed + merge + + none + + + + net.adamcin.oakpal + oakpal-caliper.ui.apps.structure + + + + + + + + + net.adamcin.oakpal + oakpal-caliper.ui.apps.structure + 2.2.0-SNAPSHOT + zip + + + diff --git a/calipers/ui.apps/src/main/content/META-INF/vault/filter.xml b/calipers/ui.apps/src/main/content/META-INF/vault/filter.xml new file mode 100644 index 000000000..7cd852469 --- /dev/null +++ b/calipers/ui.apps/src/main/content/META-INF/vault/filter.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/calipers/ui.apps/src/main/content/jcr_root/.gitkeep b/calipers/ui.apps/src/main/content/jcr_root/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/calipers/ui.apps/src/main/content/jcr_root/apps/.content.xml b/calipers/ui.apps/src/main/content/jcr_root/apps/.content.xml new file mode 100644 index 000000000..fe3716e8c --- /dev/null +++ b/calipers/ui.apps/src/main/content/jcr_root/apps/.content.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/calipers/ui.apps/src/main/content/jcr_root/apps/oakpal-caliper/.content.xml b/calipers/ui.apps/src/main/content/jcr_root/apps/oakpal-caliper/.content.xml new file mode 100644 index 000000000..fe3716e8c --- /dev/null +++ b/calipers/ui.apps/src/main/content/jcr_root/apps/oakpal-caliper/.content.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/calipers/ui.apps/src/main/content/jcr_root/apps/oakpal-caliper/config/org.apache.sling.jcr.repoinit.RepositoryInitializer~init.config b/calipers/ui.apps/src/main/content/jcr_root/apps/oakpal-caliper/config/org.apache.sling.jcr.repoinit.RepositoryInitializer~init.config new file mode 100644 index 000000000..93904170c --- /dev/null +++ b/calipers/ui.apps/src/main/content/jcr_root/apps/oakpal-caliper/config/org.apache.sling.jcr.repoinit.RepositoryInitializer~init.config @@ -0,0 +1,3 @@ +scripts=[ +"register nodetypes\n<<===\n<'sling'='http://sling.apache.org/jcr/sling/1.0'>\n[sling:OsgiConfig] > nt:unstructured, nt:hierarchyNode\n===>>\n" +] \ No newline at end of file diff --git a/calipers/ui.content.suba2/pom.xml b/calipers/ui.content.suba2/pom.xml new file mode 100644 index 000000000..d4a0a6717 --- /dev/null +++ b/calipers/ui.content.suba2/pom.xml @@ -0,0 +1,78 @@ + + + + + 4.0.0 + + + net.adamcin.oakpal + oakpal-calipers + 2.2.0-SNAPSHOT + + + oakpal-caliper.ui.content.suba2 + content-package + + OakPAL - Calipers - ui.content.suba2 + OakPAL Caliper - well-formed ui.content subpackage alpha-two. listed first, installed second. + + 2017 + + + https://github.com/adamcin/oakpal + scm:git:git@github.com:adamcin/oakpal.git + scm:git://github.com/adamcin/oakpal.git + HEAD + + + + + + + + + org.apache.jackrabbit + filevault-package-maven-plugin + true + + net.adamcin.oakpal + oakpal-caliper.ui.content.suba2 + content + merge + + none + + + + net.adamcin.oakpal + oakpal-caliper.ui.apps + ${project.version} + + + + + + + + + net.adamcin.oakpal + oakpal-caliper.ui.apps + 2.2.0-SNAPSHOT + zip + + + diff --git a/calipers/ui.content.suba2/src/main/content/META-INF/vault/filter.xml b/calipers/ui.content.suba2/src/main/content/META-INF/vault/filter.xml new file mode 100644 index 000000000..62e656e16 --- /dev/null +++ b/calipers/ui.content.suba2/src/main/content/META-INF/vault/filter.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/calipers/ui.content.suba2/src/main/content/jcr_root/content/oakpal-caliper/.content.xml b/calipers/ui.content.suba2/src/main/content/jcr_root/content/oakpal-caliper/.content.xml new file mode 100644 index 000000000..fe3716e8c --- /dev/null +++ b/calipers/ui.content.suba2/src/main/content/jcr_root/content/oakpal-caliper/.content.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/calipers/ui.content.subb3/pom.xml b/calipers/ui.content.subb3/pom.xml new file mode 100644 index 000000000..1185cb50c --- /dev/null +++ b/calipers/ui.content.subb3/pom.xml @@ -0,0 +1,78 @@ + + + + + 4.0.0 + + + net.adamcin.oakpal + oakpal-calipers + 2.2.0-SNAPSHOT + + + oakpal-caliper.ui.content.subb3 + content-package + + OakPAL - Calipers - ui.content.subb3 + OakPAL Caliper - well-formed ui.content subpackage bravo-three. listed second, installed third. + + 2017 + + + https://github.com/adamcin/oakpal + scm:git:git@github.com:adamcin/oakpal.git + scm:git://github.com/adamcin/oakpal.git + HEAD + + + + + + + + + org.apache.jackrabbit + filevault-package-maven-plugin + true + + net.adamcin.oakpal + oakpal-caliper.ui.content.subb3 + content + merge + + none + + + + net.adamcin.oakpal + oakpal-caliper.ui.apps + ${project.version} + + + + + + + + + net.adamcin.oakpal + oakpal-caliper.ui.apps + 2.2.0-SNAPSHOT + zip + + + diff --git a/calipers/ui.content.subb3/src/main/content/META-INF/vault/filter.xml b/calipers/ui.content.subb3/src/main/content/META-INF/vault/filter.xml new file mode 100644 index 000000000..62e656e16 --- /dev/null +++ b/calipers/ui.content.subb3/src/main/content/META-INF/vault/filter.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/calipers/ui.content.subb3/src/main/content/jcr_root/content/oakpal-caliper/.content.xml b/calipers/ui.content.subb3/src/main/content/jcr_root/content/oakpal-caliper/.content.xml new file mode 100644 index 000000000..fe3716e8c --- /dev/null +++ b/calipers/ui.content.subb3/src/main/content/jcr_root/content/oakpal-caliper/.content.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/calipers/ui.content.subc1/pom.xml b/calipers/ui.content.subc1/pom.xml new file mode 100644 index 000000000..be804e89c --- /dev/null +++ b/calipers/ui.content.subc1/pom.xml @@ -0,0 +1,78 @@ + + + + + 4.0.0 + + + net.adamcin.oakpal + oakpal-calipers + 2.2.0-SNAPSHOT + + + oakpal-caliper.ui.content.subc1 + content-package + + OakPAL - Calipers - ui.content.subc1 + OakPAL Caliper - well-formed ui.content subpackage charlie-one. listed third, installed first. + + 2017 + + + https://github.com/adamcin/oakpal + scm:git:git@github.com:adamcin/oakpal.git + scm:git://github.com/adamcin/oakpal.git + HEAD + + + + + + + + + org.apache.jackrabbit + filevault-package-maven-plugin + true + + net.adamcin.oakpal + oakpal-caliper.ui.content.subc1 + content + merge + + none + + + + net.adamcin.oakpal + oakpal-caliper.ui.apps + ${project.version} + + + + + + + + + net.adamcin.oakpal + oakpal-caliper.ui.apps + 2.2.0-SNAPSHOT + zip + + + diff --git a/calipers/ui.content.subc1/src/main/content/META-INF/vault/filter.xml b/calipers/ui.content.subc1/src/main/content/META-INF/vault/filter.xml new file mode 100644 index 000000000..62e656e16 --- /dev/null +++ b/calipers/ui.content.subc1/src/main/content/META-INF/vault/filter.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/calipers/ui.content.subc1/src/main/content/jcr_root/content/oakpal-caliper/.content.xml b/calipers/ui.content.subc1/src/main/content/jcr_root/content/oakpal-caliper/.content.xml new file mode 100644 index 000000000..fe3716e8c --- /dev/null +++ b/calipers/ui.content.subc1/src/main/content/jcr_root/content/oakpal-caliper/.content.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/calipers/ui.content/pom.xml b/calipers/ui.content/pom.xml new file mode 100644 index 000000000..e6fd083e6 --- /dev/null +++ b/calipers/ui.content/pom.xml @@ -0,0 +1,114 @@ + + + + + 4.0.0 + + + net.adamcin.oakpal + oakpal-calipers + 2.2.0-SNAPSHOT + + + oakpal-caliper.ui.content + content-package + + OakPAL - Calipers - ui.content + OakPAL Caliper - well-formed ui.content package containing non-/apps/ content. + + 2017 + + + https://github.com/adamcin/oakpal + scm:git:git@github.com:adamcin/oakpal.git + scm:git://github.com/adamcin/oakpal.git + HEAD + + + + + + + + + org.apache.jackrabbit + filevault-package-maven-plugin + true + + net.adamcin.oakpal + oakpal-caliper.ui.content + content + merge + + none + oakpal-caliper.ui.content.subc1,oakpal-caliper.ui.content.suba2;ignore,oakpal-caliper.ui.content.subb3 + + + + net.adamcin.oakpal + oakpal-caliper.ui.apps + ${project.version} + + + + + net.adamcin.oakpal + oakpal-caliper.ui.content.suba2 + true + + + net.adamcin.oakpal + oakpal-caliper.ui.content.subb3 + true + + + net.adamcin.oakpal + oakpal-caliper.ui.content.subc1 + true + + + + + + + + + net.adamcin.oakpal + oakpal-caliper.ui.apps + 2.2.0-SNAPSHOT + zip + + + net.adamcin.oakpal + oakpal-caliper.ui.content.suba2 + 2.2.0-SNAPSHOT + zip + + + net.adamcin.oakpal + oakpal-caliper.ui.content.subb3 + 2.2.0-SNAPSHOT + zip + + + net.adamcin.oakpal + oakpal-caliper.ui.content.subc1 + 2.2.0-SNAPSHOT + zip + + + diff --git a/calipers/ui.content/src/main/content/META-INF/vault/filter.xml b/calipers/ui.content/src/main/content/META-INF/vault/filter.xml new file mode 100644 index 000000000..62e656e16 --- /dev/null +++ b/calipers/ui.content/src/main/content/META-INF/vault/filter.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/calipers/ui.content/src/main/content/jcr_root/content/oakpal-caliper/.content.xml b/calipers/ui.content/src/main/content/jcr_root/content/oakpal-caliper/.content.xml new file mode 100644 index 000000000..fe3716e8c --- /dev/null +++ b/calipers/ui.content/src/main/content/jcr_root/content/oakpal-caliper/.content.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index f1b2855ea..e32ac68f3 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -59,6 +59,9 @@ unpack-test-packages + + copy-caliper-all + @@ -279,6 +282,12 @@ oakpal-testing test + + net.adamcin.oakpal + oakpal-caliper.all + zip + test + ch.qos.logback logback-classic diff --git a/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java b/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java index 4b6546534..fbb12a742 100644 --- a/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java +++ b/core/src/main/java/net/adamcin/oakpal/core/checks/SlingJcrInstaller.java @@ -96,7 +96,7 @@ public void simulateSling(final SlingSimulator slingSimulator, final Set } Pattern compileInstallPattern(final @NotNull Set runModes) { - return Pattern.compile(String.format("^(/[^/]*)*/(install|config)(.(%s))*$", + return Pattern.compile(String.format("^(/[^/]*)*/(install|config)(\\.(%s))*$", String.join("|", runModes))); } diff --git a/core/src/test/java/net/adamcin/oakpal/it/GrandTourIT.java b/core/src/test/java/net/adamcin/oakpal/it/GrandTourIT.java new file mode 100644 index 000000000..eebc44a43 --- /dev/null +++ b/core/src/test/java/net/adamcin/oakpal/it/GrandTourIT.java @@ -0,0 +1,109 @@ +/* + * Copyright 2020 Mark Adamcin + * + * Licensed 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 net.adamcin.oakpal.it; + +import net.adamcin.oakpal.api.ProgressCheck; +import net.adamcin.oakpal.api.Violation; +import net.adamcin.oakpal.core.OakpalPlan; +import net.adamcin.oakpal.core.checks.SlingJcrInstaller; +import net.adamcin.oakpal.core.sling.DefaultSlingSimulator; +import net.adamcin.oakpal.testing.TestPackageUtil; +import org.apache.jackrabbit.vault.packaging.PackageId; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static net.adamcin.oakpal.api.JavaxJson.obj; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class GrandTourIT { + + private File grandTourPackage = TestPackageUtil.getCaliperPackage(); + + private List installOrderFull = Arrays.asList( + "oakpal-caliper.all", + "oakpal-caliper.ui.apps", + "oakpal-caliper.ui.apps.author", + "oakpal-caliper.ui.apps.publish", + "oakpal-caliper.ui.content", + "oakpal-caliper.ui.content.subc1", + "oakpal-caliper.ui.content.suba2", + "oakpal-caliper.ui.content.subb3" + ); + + private List installOrderNoRunModesNoSubs = Arrays.asList( + "oakpal-caliper.all", + "oakpal-caliper.ui.apps", + "oakpal-caliper.ui.content" + ); + + @Before + public void setUp() throws Exception { + assertTrue("expect grand tour package has been copied to " + grandTourPackage.getAbsolutePath(), + grandTourPackage.exists()); + } + + @Test + public void testScanNoRunModes() throws Exception { + OakpalPlan.fromJson(obj().get()).toOakMachineBuilder(null, getClass().getClassLoader()).build() + .scanPackage(grandTourPackage); + } + + @Test + public void testScanNoRunModes_captureIdentifiedPackages() throws Exception { + final List identifiedPackageIds = new ArrayList<>(); + final ProgressCheck check = new ProgressCheck() { + @Override + public void identifyPackage(final PackageId packageId, final File file) { + identifiedPackageIds.add(packageId); + } + + @Override + public void identifySubpackage(final PackageId packageId, final PackageId parentId) { + identifiedPackageIds.add(packageId); + } + + @Override + public void identifyEmbeddedPackage(final PackageId packageId, final PackageId parentId, final String jcrPath) { + identifiedPackageIds.add(packageId); + } + + @Override + public Collection getReportedViolations() { + return Collections.emptyList(); + } + }; + OakpalPlan.fromJson(obj().get()) + .toOakMachineBuilder(null, getClass().getClassLoader()) + .withProgressCheck(check, new SlingJcrInstaller().newInstance(obj().get())) + .withSlingSimulator(DefaultSlingSimulator.instance()) + .withSubpackageSilencer((sub, parent) -> true) + .build() + .scanPackage(grandTourPackage); + + assertEquals("expect identified packages in order", installOrderNoRunModesNoSubs, + identifiedPackageIds.stream().map(PackageId::getName).collect(Collectors.toList())); + } +} diff --git a/pom.xml b/pom.xml index 2e7adf459..90d42fcf6 100644 --- a/pom.xml +++ b/pom.xml @@ -64,6 +64,7 @@ webster maven testing + calipers @@ -157,6 +158,26 @@ + + copy-caliper-all + generate-test-resources + + copy + + + true + + + net.adamcin.oakpal + oakpal-caliper.all + ${project.version} + zip + true + ${project.build.testOutputDirectory} + + + + @@ -500,6 +521,12 @@ oakpal-testing 2.2.0-SNAPSHOT + + net.adamcin.oakpal + oakpal-caliper.all + 2.2.0-SNAPSHOT + zip + net.adamcin.oakpal oakpal-api diff --git a/testing/pom.xml b/testing/pom.xml index 2c28b89d5..f2a83a37d 100644 --- a/testing/pom.xml +++ b/testing/pom.xml @@ -59,6 +59,9 @@ unpack-test-packages + + copy-caliper-all + @@ -81,6 +84,12 @@ org.slf4j slf4j-api + + net.adamcin.oakpal + oakpal-caliper.all + zip + test + commons-io diff --git a/testing/src/main/java/net/adamcin/oakpal/testing/TestPackageUtil.java b/testing/src/main/java/net/adamcin/oakpal/testing/TestPackageUtil.java index fda98a624..fc5eb134b 100644 --- a/testing/src/main/java/net/adamcin/oakpal/testing/TestPackageUtil.java +++ b/testing/src/main/java/net/adamcin/oakpal/testing/TestPackageUtil.java @@ -88,6 +88,10 @@ public static File prepareTestPackage(final String filename) throws IOException return file; } + public static File getCaliperPackage() { + return Paths.get("target/test-classes/oakpal-caliper.all.zip").toFile(); + } + public static File prepareTestPackageFromFolder(final @NotNull String filename, final @NotNull File srcFolder) throws IOException { final File absFile = srcFolder.getAbsoluteFile(); diff --git a/testing/src/test/java/net/adamcin/oakpal/testing/TestPackageUtilTest.java b/testing/src/test/java/net/adamcin/oakpal/testing/TestPackageUtilTest.java index 8553d8935..272c1c335 100644 --- a/testing/src/test/java/net/adamcin/oakpal/testing/TestPackageUtilTest.java +++ b/testing/src/test/java/net/adamcin/oakpal/testing/TestPackageUtilTest.java @@ -171,4 +171,10 @@ public void testBuildJarFromDir_parentDirIsFile() throws Exception { TestPackageUtil.buildJarFromDir(new File("src/test/resources/extracted/simple"), outJar, Collections.emptyMap()); } + + @Test + public void testGetCaliperPackage() { + File file = TestPackageUtil.getCaliperPackage(); + assertTrue("expect file exists", file.exists()); + } } From dc68280546d11925eda9b9d189f97f6c9c9de1e6 Mon Sep 17 00:00:00 2001 From: Mark Adamcin Date: Thu, 27 Aug 2020 19:17:22 -0700 Subject: [PATCH 10/10] updated site docs --- calipers/all/README.md | 1 + calipers/all/src/site/site.xml | 23 +++++++++++++++++++ calipers/src/site/site.xml | 23 +++++++++++++++++++ calipers/ui.apps.author/README.md | 1 + .../src/main/content/jcr_root/.gitkeep | 0 calipers/ui.apps.author/src/site/site.xml | 23 +++++++++++++++++++ calipers/ui.apps.publish/README.md | 1 + .../src/main/content/jcr_root/.gitkeep | 0 calipers/ui.apps.publish/src/site/site.xml | 23 +++++++++++++++++++ calipers/ui.apps.structure/README.md | 1 + calipers/ui.apps.structure/src/site/site.xml | 23 +++++++++++++++++++ calipers/ui.apps/README.md | 1 + .../src/main/content/jcr_root/.gitkeep | 0 calipers/ui.apps/src/site/site.xml | 23 +++++++++++++++++++ calipers/ui.content.suba2/README.md | 1 + calipers/ui.content.suba2/src/site/site.xml | 23 +++++++++++++++++++ calipers/ui.content.subb3/README.md | 1 + calipers/ui.content.subb3/src/site/site.xml | 23 +++++++++++++++++++ calipers/ui.content.subc1/README.md | 1 + calipers/ui.content.subc1/src/site/site.xml | 23 +++++++++++++++++++ calipers/ui.content/README.md | 1 + calipers/ui.content/src/site/site.xml | 23 +++++++++++++++++++ 22 files changed, 239 insertions(+) create mode 100644 calipers/all/README.md create mode 100644 calipers/all/src/site/site.xml create mode 100644 calipers/src/site/site.xml create mode 100644 calipers/ui.apps.author/README.md delete mode 100644 calipers/ui.apps.author/src/main/content/jcr_root/.gitkeep create mode 100644 calipers/ui.apps.author/src/site/site.xml create mode 100644 calipers/ui.apps.publish/README.md delete mode 100644 calipers/ui.apps.publish/src/main/content/jcr_root/.gitkeep create mode 100644 calipers/ui.apps.publish/src/site/site.xml create mode 100644 calipers/ui.apps.structure/README.md create mode 100644 calipers/ui.apps.structure/src/site/site.xml create mode 100644 calipers/ui.apps/README.md delete mode 100644 calipers/ui.apps/src/main/content/jcr_root/.gitkeep create mode 100644 calipers/ui.apps/src/site/site.xml create mode 100644 calipers/ui.content.suba2/README.md create mode 100644 calipers/ui.content.suba2/src/site/site.xml create mode 100644 calipers/ui.content.subb3/README.md create mode 100644 calipers/ui.content.subb3/src/site/site.xml create mode 100644 calipers/ui.content.subc1/README.md create mode 100644 calipers/ui.content.subc1/src/site/site.xml create mode 100644 calipers/ui.content/README.md create mode 100644 calipers/ui.content/src/site/site.xml diff --git a/calipers/all/README.md b/calipers/all/README.md new file mode 100644 index 000000000..a4a0e44de --- /dev/null +++ b/calipers/all/README.md @@ -0,0 +1 @@ +# oakpal-caliper.all \ No newline at end of file diff --git a/calipers/all/src/site/site.xml b/calipers/all/src/site/site.xml new file mode 100644 index 000000000..6bf21e81c --- /dev/null +++ b/calipers/all/src/site/site.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/calipers/src/site/site.xml b/calipers/src/site/site.xml new file mode 100644 index 000000000..6bf21e81c --- /dev/null +++ b/calipers/src/site/site.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/calipers/ui.apps.author/README.md b/calipers/ui.apps.author/README.md new file mode 100644 index 000000000..2dc366530 --- /dev/null +++ b/calipers/ui.apps.author/README.md @@ -0,0 +1 @@ +# oakpal-caliper.ui.apps.author \ No newline at end of file diff --git a/calipers/ui.apps.author/src/main/content/jcr_root/.gitkeep b/calipers/ui.apps.author/src/main/content/jcr_root/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/calipers/ui.apps.author/src/site/site.xml b/calipers/ui.apps.author/src/site/site.xml new file mode 100644 index 000000000..6bf21e81c --- /dev/null +++ b/calipers/ui.apps.author/src/site/site.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/calipers/ui.apps.publish/README.md b/calipers/ui.apps.publish/README.md new file mode 100644 index 000000000..3b985f4de --- /dev/null +++ b/calipers/ui.apps.publish/README.md @@ -0,0 +1 @@ +# oakpal-caliper.ui.apps.publish \ No newline at end of file diff --git a/calipers/ui.apps.publish/src/main/content/jcr_root/.gitkeep b/calipers/ui.apps.publish/src/main/content/jcr_root/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/calipers/ui.apps.publish/src/site/site.xml b/calipers/ui.apps.publish/src/site/site.xml new file mode 100644 index 000000000..6bf21e81c --- /dev/null +++ b/calipers/ui.apps.publish/src/site/site.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/calipers/ui.apps.structure/README.md b/calipers/ui.apps.structure/README.md new file mode 100644 index 000000000..bf56f150b --- /dev/null +++ b/calipers/ui.apps.structure/README.md @@ -0,0 +1 @@ +# oakpal-caliper.ui.apps.structure \ No newline at end of file diff --git a/calipers/ui.apps.structure/src/site/site.xml b/calipers/ui.apps.structure/src/site/site.xml new file mode 100644 index 000000000..6bf21e81c --- /dev/null +++ b/calipers/ui.apps.structure/src/site/site.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/calipers/ui.apps/README.md b/calipers/ui.apps/README.md new file mode 100644 index 000000000..091814255 --- /dev/null +++ b/calipers/ui.apps/README.md @@ -0,0 +1 @@ +# oakpal-caliper.ui.apps \ No newline at end of file diff --git a/calipers/ui.apps/src/main/content/jcr_root/.gitkeep b/calipers/ui.apps/src/main/content/jcr_root/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/calipers/ui.apps/src/site/site.xml b/calipers/ui.apps/src/site/site.xml new file mode 100644 index 000000000..6bf21e81c --- /dev/null +++ b/calipers/ui.apps/src/site/site.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/calipers/ui.content.suba2/README.md b/calipers/ui.content.suba2/README.md new file mode 100644 index 000000000..5b5a85692 --- /dev/null +++ b/calipers/ui.content.suba2/README.md @@ -0,0 +1 @@ +# oakpal-caliper.ui.content.suba2 \ No newline at end of file diff --git a/calipers/ui.content.suba2/src/site/site.xml b/calipers/ui.content.suba2/src/site/site.xml new file mode 100644 index 000000000..6bf21e81c --- /dev/null +++ b/calipers/ui.content.suba2/src/site/site.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/calipers/ui.content.subb3/README.md b/calipers/ui.content.subb3/README.md new file mode 100644 index 000000000..78bd7319d --- /dev/null +++ b/calipers/ui.content.subb3/README.md @@ -0,0 +1 @@ +# oakpal-caliper.ui.content.subb3 \ No newline at end of file diff --git a/calipers/ui.content.subb3/src/site/site.xml b/calipers/ui.content.subb3/src/site/site.xml new file mode 100644 index 000000000..6bf21e81c --- /dev/null +++ b/calipers/ui.content.subb3/src/site/site.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/calipers/ui.content.subc1/README.md b/calipers/ui.content.subc1/README.md new file mode 100644 index 000000000..38ec9057e --- /dev/null +++ b/calipers/ui.content.subc1/README.md @@ -0,0 +1 @@ +# oakpal-caliper.ui.content.subc1 \ No newline at end of file diff --git a/calipers/ui.content.subc1/src/site/site.xml b/calipers/ui.content.subc1/src/site/site.xml new file mode 100644 index 000000000..6bf21e81c --- /dev/null +++ b/calipers/ui.content.subc1/src/site/site.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/calipers/ui.content/README.md b/calipers/ui.content/README.md new file mode 100644 index 000000000..b2496fe7d --- /dev/null +++ b/calipers/ui.content/README.md @@ -0,0 +1 @@ +# oakpal-caliper.ui.content \ No newline at end of file diff --git a/calipers/ui.content/src/site/site.xml b/calipers/ui.content/src/site/site.xml new file mode 100644 index 000000000..6bf21e81c --- /dev/null +++ b/calipers/ui.content/src/site/site.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file