Merge pull request #20 from apache/SLING-8591-3

SLING-8591 - Hook to support custom code when installing or deleting …
diff --git a/src/main/java/org/apache/sling/distribution/packaging/PackageInstallHook.java b/src/main/java/org/apache/sling/distribution/packaging/PackageInstallHook.java
new file mode 100644
index 0000000..ad806fc
--- /dev/null
+++ b/src/main/java/org/apache/sling/distribution/packaging/PackageInstallHook.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.distribution.packaging;
+
+import org.apache.sling.api.resource.ResourceResolver;
+
+import aQute.bnd.annotation.ProviderType;
+
+/**
+ * Callback that allows to run code after a DistributionPackage is installed.
+ */
+@ProviderType
+public interface PackageInstallHook {
+    public static final String NOOP_NAME = "noop_install_hook";
+    public static final String NOOP_FILTER = "(name=" + PackageInstallHook.NOOP_NAME + ")";
+    
+    /**
+     * Will be called after a package was installed into the repository
+     * @param resourceResolver 
+     * @param distPackage
+     */
+    void onPostAdd(ResourceResolver resourceResolver, DistributionPackage distPackage);
+    
+    /**
+     * Will be called after a package was installed into the repository
+     * @param resourceResolver 
+     * @param distPackage
+     */
+    void onPreRemove(ResourceResolver resourceResolver, DistributionPackage distPackage);
+}
diff --git a/src/main/java/org/apache/sling/distribution/packaging/impl/AbstractDistributionPackageBuilder.java b/src/main/java/org/apache/sling/distribution/packaging/impl/AbstractDistributionPackageBuilder.java
index 0993667..21dec3d 100644
--- a/src/main/java/org/apache/sling/distribution/packaging/impl/AbstractDistributionPackageBuilder.java
+++ b/src/main/java/org/apache/sling/distribution/packaging/impl/AbstractDistributionPackageBuilder.java
@@ -35,6 +35,7 @@
 import org.apache.sling.distribution.packaging.DistributionPackage;
 import org.apache.sling.distribution.packaging.DistributionPackageBuilder;
 import org.apache.sling.distribution.packaging.DistributionPackageInfo;
+import org.apache.sling.distribution.packaging.PackageInstallHook;
 import org.apache.sling.distribution.serialization.impl.vlt.VltUtils;
 import org.apache.sling.distribution.util.DistributionJcrUtils;
 import org.jetbrains.annotations.NotNull;
@@ -51,8 +52,11 @@
 
     private final String type;
 
-    AbstractDistributionPackageBuilder(String type) {
+    private PackageInstallHook installHook;
+
+    AbstractDistributionPackageBuilder(String type, PackageInstallHook installHook) {
         this.type = type;
+        this.installHook = installHook;
     }
 
     public String getType() {
@@ -153,7 +157,8 @@
         boolean installed;
         // not a simple package
         if (distributionPackage == null) {
-            installed = installPackageInternal(resourceResolver, stream);
+            installPackageInternal(resourceResolver, stream);
+            installed = true;
         } else {
             installed = installPackage(resourceResolver, distributionPackage);
             packageInfo.putAll(distributionPackage.getInfo());
@@ -170,6 +175,7 @@
         Session session = null;
         try {
             if (distributionPackage != null) {
+                installHook.onPreRemove(resourceResolver, distributionPackage);
                 session = getSession(resourceResolver);
                 for (String path : distributionPackage.getInfo().getPaths()) {
                     if (session.itemExists(path)) {
@@ -193,7 +199,9 @@
         InputStream inputStream = null;
         try {
             inputStream = distributionPackage.createInputStream();
-            return installPackageInternal(resourceResolver, inputStream);
+            installPackageInternal(resourceResolver, inputStream);
+            installHook.onPostAdd(resourceResolver, distributionPackage);
+            return true;
         } catch (IOException e) {
             throw new DistributionException(e);
         } finally {
@@ -251,7 +259,7 @@
             throws DistributionException;
 
 
-    protected abstract boolean installPackageInternal(@NotNull ResourceResolver resourceResolver, @NotNull InputStream stream)
+    protected abstract void installPackageInternal(@NotNull ResourceResolver resourceResolver, @NotNull InputStream stream)
             throws DistributionException;
 
     @Nullable
diff --git a/src/main/java/org/apache/sling/distribution/packaging/impl/FileDistributionPackageBuilder.java b/src/main/java/org/apache/sling/distribution/packaging/impl/FileDistributionPackageBuilder.java
index a79a7a2..8e649ee 100644
--- a/src/main/java/org/apache/sling/distribution/packaging/impl/FileDistributionPackageBuilder.java
+++ b/src/main/java/org/apache/sling/distribution/packaging/impl/FileDistributionPackageBuilder.java
@@ -37,6 +37,7 @@
 import org.apache.sling.distribution.common.DistributionException;
 import org.apache.sling.distribution.packaging.DistributionPackage;
 import org.apache.sling.distribution.packaging.DistributionPackageBuilder;
+import org.apache.sling.distribution.packaging.PackageInstallHook;
 import org.apache.sling.distribution.serialization.DistributionContentSerializer;
 import org.apache.sling.distribution.serialization.DistributionExportFilter;
 import org.apache.sling.distribution.serialization.DistributionExportOptions;
@@ -64,8 +65,9 @@
                                           DistributionContentSerializer distributionContentSerializer,
                                           String tempFilesFolder,
                                           String digestAlgorithm, String[] nodeFilters,
-                                          String[] propertyFilters) {
-        super(type);
+                                          String[] propertyFilters,
+                                          PackageInstallHook postInstallHook) {
+        super(type, postInstallHook);
         this.distributionContentSerializer = distributionContentSerializer;
         this.nodeFilters = VltUtils.parseFilters(nodeFilters);
         this.propertyFilters = VltUtils.parseFilters(propertyFilters);
@@ -151,11 +153,10 @@
     }
 
     @Override
-    protected boolean installPackageInternal(@NotNull ResourceResolver resourceResolver, @NotNull InputStream inputStream)
+    protected void installPackageInternal(@NotNull ResourceResolver resourceResolver, @NotNull InputStream inputStream)
             throws DistributionException {
         try {
             distributionContentSerializer.importFromStream(resourceResolver, inputStream);
-            return true;
         } finally {
             IOUtils.closeQuietly(inputStream);
         }
diff --git a/src/main/java/org/apache/sling/distribution/packaging/impl/InMemoryDistributionPackageBuilder.java b/src/main/java/org/apache/sling/distribution/packaging/impl/InMemoryDistributionPackageBuilder.java
index d55b77e..ad6b4a1 100644
--- a/src/main/java/org/apache/sling/distribution/packaging/impl/InMemoryDistributionPackageBuilder.java
+++ b/src/main/java/org/apache/sling/distribution/packaging/impl/InMemoryDistributionPackageBuilder.java
@@ -33,6 +33,7 @@
 import org.apache.sling.distribution.DistributionRequest;
 import org.apache.sling.distribution.common.DistributionException;
 import org.apache.sling.distribution.packaging.DistributionPackage;
+import org.apache.sling.distribution.packaging.PackageInstallHook;
 import org.apache.sling.distribution.serialization.DistributionContentSerializer;
 import org.apache.sling.distribution.serialization.DistributionExportFilter;
 import org.apache.sling.distribution.serialization.DistributionExportOptions;
@@ -58,8 +59,9 @@
     public InMemoryDistributionPackageBuilder(@NotNull String type,
                                               @NotNull DistributionContentSerializer serializer,
                                               @Nullable String[] nodeFilters,
-                                              @Nullable String[] propertyFilters) {
-        super(type);
+                                              @Nullable String[] propertyFilters,
+                                              PackageInstallHook postInstallHook) {
+        super(type, postInstallHook);
         this.serializer = serializer;
         this.nodeFilters = VltUtils.parseFilters(nodeFilters);
         this.propertyFilters = VltUtils.parseFilters(propertyFilters);
@@ -109,12 +111,11 @@
     }
 
     @Override
-    protected boolean installPackageInternal(@NotNull ResourceResolver resourceResolver,
+    protected void installPackageInternal(@NotNull ResourceResolver resourceResolver,
                                              @NotNull InputStream inputStream)
             throws DistributionException {
         try {
             serializer.importFromStream(resourceResolver, inputStream);
-            return true;
         } finally {
             IOUtils.closeQuietly(inputStream);
         }
diff --git a/src/main/java/org/apache/sling/distribution/packaging/impl/NoopPackageInstallHook.java b/src/main/java/org/apache/sling/distribution/packaging/impl/NoopPackageInstallHook.java
new file mode 100644
index 0000000..a87f29c
--- /dev/null
+++ b/src/main/java/org/apache/sling/distribution/packaging/impl/NoopPackageInstallHook.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.distribution.packaging.impl;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.distribution.packaging.DistributionPackage;
+import org.apache.sling.distribution.packaging.PackageInstallHook;
+
+/**
+ * Default hook that simply does nothing
+ */
+@Component
+@Service(PackageInstallHook.class)
+@Property(name = "name", value = "Builder name: {name}")
+public class NoopPackageInstallHook implements PackageInstallHook {
+
+    @Override
+    public void onPostAdd(ResourceResolver resourceResolver, DistributionPackage distPackage) {
+        // Noop
+    }
+
+    @Override
+    public void onPreRemove(ResourceResolver resourceResolver, DistributionPackage distPackage) {
+        // Noop
+    }
+}
diff --git a/src/main/java/org/apache/sling/distribution/packaging/impl/ResourceDistributionPackageBuilder.java b/src/main/java/org/apache/sling/distribution/packaging/impl/ResourceDistributionPackageBuilder.java
index 919d3d9..3670f5b 100644
--- a/src/main/java/org/apache/sling/distribution/packaging/impl/ResourceDistributionPackageBuilder.java
+++ b/src/main/java/org/apache/sling/distribution/packaging/impl/ResourceDistributionPackageBuilder.java
@@ -44,6 +44,7 @@
 import org.apache.sling.distribution.DistributionRequest;
 import org.apache.sling.distribution.common.DistributionException;
 import org.apache.sling.distribution.packaging.DistributionPackage;
+import org.apache.sling.distribution.packaging.PackageInstallHook;
 import org.apache.sling.distribution.serialization.DistributionContentSerializer;
 import org.apache.sling.distribution.serialization.DistributionExportFilter;
 import org.apache.sling.distribution.serialization.DistributionExportOptions;
@@ -76,8 +77,9 @@
                                               MemoryUnit memoryUnit,
                                               boolean useOffHeapMemory,
                                               String digestAlgorithm, String[] nodeFilters,
-                                              String[] propertyFilters) {
-        super(type);
+                                              String[] propertyFilters, 
+                                              PackageInstallHook postInstallHook) {
+        super(type, postInstallHook);
         this.distributionContentSerializer = distributionContentSerializer;
         this.nodeFilters = VltUtils.parseFilters(nodeFilters);
         this.propertyFilters = VltUtils.parseFilters(propertyFilters);
@@ -158,11 +160,10 @@
     }
 
     @Override
-    protected boolean installPackageInternal(@NotNull ResourceResolver resourceResolver, @NotNull InputStream inputStream)
+    protected void installPackageInternal(@NotNull ResourceResolver resourceResolver, @NotNull InputStream inputStream)
             throws DistributionException {
         try {
             distributionContentSerializer.importFromStream(resourceResolver, inputStream);
-            return true;
         } finally {
             IOUtils.closeQuietly(inputStream);
         }
diff --git a/src/main/java/org/apache/sling/distribution/packaging/package-info.java b/src/main/java/org/apache/sling/distribution/packaging/package-info.java
index 8f378be..e0a3160 100644
--- a/src/main/java/org/apache/sling/distribution/packaging/package-info.java
+++ b/src/main/java/org/apache/sling/distribution/packaging/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@Version("0.0.1")
+@Version("0.1.0")
 package org.apache.sling.distribution.packaging;
 
 import aQute.bnd.annotation.Version;
diff --git a/src/main/java/org/apache/sling/distribution/serialization/impl/DistributionPackageBuilderFactory.java b/src/main/java/org/apache/sling/distribution/serialization/impl/DistributionPackageBuilderFactory.java
index 09b8c9b..7d7f30a 100644
--- a/src/main/java/org/apache/sling/distribution/serialization/impl/DistributionPackageBuilderFactory.java
+++ b/src/main/java/org/apache/sling/distribution/serialization/impl/DistributionPackageBuilderFactory.java
@@ -44,6 +44,7 @@
 import org.apache.sling.distribution.packaging.DistributionPackage;
 import org.apache.sling.distribution.packaging.DistributionPackageBuilder;
 import org.apache.sling.distribution.packaging.DistributionPackageInfo;
+import org.apache.sling.distribution.packaging.PackageInstallHook;
 import org.apache.sling.distribution.packaging.impl.FileDistributionPackageBuilder;
 import org.apache.sling.distribution.packaging.impl.InMemoryDistributionPackageBuilder;
 import org.apache.sling.distribution.packaging.impl.ResourceDistributionPackageBuilder;
@@ -193,6 +194,10 @@
 
     private ServiceRegistration packageCleanup = null;
 
+    @Property(name = "installHook.target", label = "install hook", description = "Hook to be called after package was installed and before package will be removed.", value = PackageInstallHook.NOOP_FILTER)
+    @Reference
+    private PackageInstallHook installHook;
+
     @Activate
     public void activate(BundleContext context,
                          Map<String, Object> config) {
@@ -209,15 +214,15 @@
 
         DistributionPackageBuilder wrapped;
         if ("file".equals(persistenceType)) {
-            wrapped = new FileDistributionPackageBuilder(contentSerializer.getName(), contentSerializer, tempFsFolder, digestAlgorithm, nodeFilters, propertyFilters);
+            wrapped = new FileDistributionPackageBuilder(contentSerializer.getName(), contentSerializer, tempFsFolder, digestAlgorithm, nodeFilters, propertyFilters, installHook);
         } else if ("inmemory".equals(persistenceType)) {
-            wrapped = new InMemoryDistributionPackageBuilder(contentSerializer.getName(), contentSerializer, nodeFilters, propertyFilters);
+            wrapped = new InMemoryDistributionPackageBuilder(contentSerializer.getName(), contentSerializer, nodeFilters, propertyFilters, installHook);
         } else {
             final int fileThreshold = PropertiesUtil.toInteger(config.get(FILE_THRESHOLD), DEFAULT_FILE_THRESHOLD_VALUE);
             String memoryUnitName = PropertiesUtil.toString(config.get(MEMORY_UNIT), DEFAULT_MEMORY_UNIT);
             final MemoryUnit memoryUnit = MemoryUnit.valueOf(memoryUnitName);
             final boolean useOffHeapMemory = PropertiesUtil.toBoolean(config.get(USE_OFF_HEAP_MEMORY), DEFAULT_USE_OFF_HEAP_MEMORY);
-            ResourceDistributionPackageBuilder resourceDistributionPackageBuilder = new ResourceDistributionPackageBuilder(contentSerializer.getName(), contentSerializer, tempFsFolder, fileThreshold, memoryUnit, useOffHeapMemory, digestAlgorithm, nodeFilters, propertyFilters);
+            ResourceDistributionPackageBuilder resourceDistributionPackageBuilder = new ResourceDistributionPackageBuilder(contentSerializer.getName(), contentSerializer, tempFsFolder, fileThreshold, memoryUnit, useOffHeapMemory, digestAlgorithm, nodeFilters, propertyFilters, installHook);
             Runnable cleanup = new ResourceDistributionPackageCleanup(resolverFactory, resourceDistributionPackageBuilder);
             Dictionary<String, Object> props = new Hashtable<String, Object>();
             props.put(Scheduler.PROPERTY_SCHEDULER_CONCURRENT, false);
diff --git a/src/main/java/org/apache/sling/distribution/serialization/impl/vlt/VaultDistributionPackageBuilderFactory.java b/src/main/java/org/apache/sling/distribution/serialization/impl/vlt/VaultDistributionPackageBuilderFactory.java
index cffed94..9db58a2 100644
--- a/src/main/java/org/apache/sling/distribution/serialization/impl/vlt/VaultDistributionPackageBuilderFactory.java
+++ b/src/main/java/org/apache/sling/distribution/serialization/impl/vlt/VaultDistributionPackageBuilderFactory.java
@@ -34,6 +34,7 @@
 import org.apache.sling.distribution.packaging.DistributionPackage;
 import org.apache.sling.distribution.packaging.DistributionPackageBuilder;
 import org.apache.sling.distribution.packaging.DistributionPackageInfo;
+import org.apache.sling.distribution.packaging.PackageInstallHook;
 import org.apache.sling.distribution.packaging.impl.FileDistributionPackageBuilder;
 import org.apache.sling.distribution.packaging.impl.InMemoryDistributionPackageBuilder;
 import org.apache.sling.distribution.packaging.impl.ResourceDistributionPackageBuilder;
@@ -226,6 +227,10 @@
 
     private MonitoringDistributionPackageBuilder packageBuilder;
 
+    @Property(name = "postInstallHook.target", label = "Post install hook", description = "Hook to be called after package was installed.",
+            value = PackageInstallHook.NOOP_FILTER)
+    @Reference
+    private PackageInstallHook postInstallHook;
 
     @Activate
     public void activate(BundleContext context, Map<String, Object> config) {
@@ -271,15 +276,15 @@
 
         DistributionPackageBuilder wrapped;
         if ("filevlt".equals(type)) {
-            wrapped = new FileDistributionPackageBuilder(name, contentSerializer, tempFsFolder, digestAlgorithm, packageNodeFilters, packagePropertyFilters);
+            wrapped = new FileDistributionPackageBuilder(name, contentSerializer, tempFsFolder, digestAlgorithm, packageNodeFilters, packagePropertyFilters, postInstallHook);
         } else if ("inmemory".equals(type)) {
-            wrapped = new InMemoryDistributionPackageBuilder(name, contentSerializer, packageNodeFilters, packagePropertyFilters);
+            wrapped = new InMemoryDistributionPackageBuilder(name, contentSerializer, packageNodeFilters, packagePropertyFilters, postInstallHook);
         } else {
             final int fileThreshold = PropertiesUtil.toInteger(config.get(FILE_THRESHOLD), DEFAULT_FILE_THRESHOLD_VALUE);
             String memoryUnitName = PropertiesUtil.toString(config.get(MEMORY_UNIT), DEFAULT_MEMORY_UNIT);
             final MemoryUnit memoryUnit = MemoryUnit.valueOf(memoryUnitName);
             final boolean useOffHeapMemory = PropertiesUtil.toBoolean(config.get(USE_OFF_HEAP_MEMORY), DEFAULT_USE_OFF_HEAP_MEMORY);
-            ResourceDistributionPackageBuilder resourceDistributionPackageBuilder = new ResourceDistributionPackageBuilder(contentSerializer.getName(), contentSerializer, tempFsFolder, fileThreshold, memoryUnit, useOffHeapMemory, digestAlgorithm, packageNodeFilters, packagePropertyFilters);
+            ResourceDistributionPackageBuilder resourceDistributionPackageBuilder = new ResourceDistributionPackageBuilder(contentSerializer.getName(), contentSerializer, tempFsFolder, fileThreshold, memoryUnit, useOffHeapMemory, digestAlgorithm, packageNodeFilters, packagePropertyFilters, postInstallHook);
             Runnable cleanup = new ResourceDistributionPackageCleanup(resolverFactory, resourceDistributionPackageBuilder);
             Dictionary<String, Object> props = new Hashtable<String, Object>();
             props.put(Scheduler.PROPERTY_SCHEDULER_CONCURRENT, false);
diff --git a/src/test/java/org/apache/sling/distribution/DistributionBaseIT.java b/src/test/java/org/apache/sling/distribution/DistributionBaseIT.java
index 98ba6c6..75a1603 100644
--- a/src/test/java/org/apache/sling/distribution/DistributionBaseIT.java
+++ b/src/test/java/org/apache/sling/distribution/DistributionBaseIT.java
@@ -27,7 +27,6 @@
 import org.apache.sling.testing.paxexam.SlingOptions;
 import org.apache.sling.testing.paxexam.TestSupport;
 import org.ops4j.pax.exam.Configuration;
-import org.ops4j.pax.exam.CoreOptions;
 import org.ops4j.pax.exam.Option;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Filter;
@@ -36,8 +35,6 @@
 
 import javax.inject.Inject;
 
-import java.io.File;
-
 import static org.apache.sling.testing.paxexam.SlingOptions.logback;
 import static org.apache.sling.testing.paxexam.SlingOptions.slingDistribution;
 import static org.apache.sling.testing.paxexam.SlingOptions.slingQuickstartOakTar;
diff --git a/src/test/java/org/apache/sling/distribution/packaging/DistributionPackageInfoTest.java b/src/test/java/org/apache/sling/distribution/packaging/DistributionPackageInfoTest.java
new file mode 100644
index 0000000..7be009a
--- /dev/null
+++ b/src/test/java/org/apache/sling/distribution/packaging/DistributionPackageInfoTest.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.distribution.packaging;
+
+import org.junit.Test;
+
+public class DistributionPackageInfoTest {
+
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalid(){
+        new DistributionPackageInfo(null);
+    }
+}
diff --git a/src/test/java/org/apache/sling/distribution/packaging/impl/FileDistributionPackageBuilderTest.java b/src/test/java/org/apache/sling/distribution/packaging/impl/FileDistributionPackageBuilderTest.java
index ea6072c..f9abb52 100644
--- a/src/test/java/org/apache/sling/distribution/packaging/impl/FileDistributionPackageBuilderTest.java
+++ b/src/test/java/org/apache/sling/distribution/packaging/impl/FileDistributionPackageBuilderTest.java
@@ -30,6 +30,7 @@
 import org.apache.sling.distribution.DistributionRequest;
 import org.apache.sling.distribution.common.DistributionException;
 import org.apache.sling.distribution.packaging.DistributionPackage;
+import org.apache.sling.distribution.packaging.PackageInstallHook;
 import org.apache.sling.distribution.serialization.DistributionContentSerializer;
 import org.apache.sling.distribution.serialization.DistributionExportOptions;
 import org.junit.Test;
@@ -38,8 +39,9 @@
 
     @Test
     public void testDefaultTempDirectory() throws DistributionException, IOException {
+        PackageInstallHook postInstallHook = new NoopPackageInstallHook();
         FileDistributionPackageBuilder builder = new FileDistributionPackageBuilder("test", new TestSerializer(), null, null, new String[0],
-                new String[0]);
+                new String[0], postInstallHook);
         DistributionPackage createdPackage = builder.createPackageForAdd(mock(ResourceResolver.class), mock(DistributionRequest.class));
 
         try {
diff --git a/src/test/java/org/apache/sling/distribution/packaging/impl/InMemoryDistributionPackageBuilderTest.java b/src/test/java/org/apache/sling/distribution/packaging/impl/InMemoryDistributionPackageBuilderTest.java
index f20dc66..03e7752 100644
--- a/src/test/java/org/apache/sling/distribution/packaging/impl/InMemoryDistributionPackageBuilderTest.java
+++ b/src/test/java/org/apache/sling/distribution/packaging/impl/InMemoryDistributionPackageBuilderTest.java
@@ -18,30 +18,92 @@
  */
 package org.apache.sling.distribution.packaging.impl;
 
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.verify;
+
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
 
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.output.ByteArrayOutputStream;
+import org.apache.sling.api.resource.LoginException;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.distribution.DistributionRequestType;
 import org.apache.sling.distribution.SimpleDistributionRequest;
 import org.apache.sling.distribution.common.DistributionException;
 import org.apache.sling.distribution.packaging.DistributionPackage;
+import org.apache.sling.distribution.packaging.DistributionPackageInfo;
+import org.apache.sling.distribution.packaging.PackageInstallHook;
 import org.apache.sling.distribution.serialization.DistributionContentSerializer;
 import org.apache.sling.distribution.serialization.DistributionExportOptions;
+import org.apache.sling.testing.mock.sling.MockSling;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
 
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.Mockito.mock;
-
+@RunWith(MockitoJUnitRunner.class)
 public class InMemoryDistributionPackageBuilderTest {
 
+    @Mock
+    private PackageInstallHook installHook;
+    
+    private ResourceResolver resolver;
+    private InMemoryDistributionPackageBuilder builder;
+    
+
+    @SuppressWarnings("deprecation")
+    @Before
+    public void before() throws LoginException {
+        resolver = MockSling.newResourceResolver(ResourceResolverType.JCR_OAK);
+        builder = new InMemoryDistributionPackageBuilder("name", new InMemDistributionContentSerializer(), new String[0], new String[0], installHook);
+    }
+
     @Test
-    public void testCreatePackage() throws Exception {
-        InMemoryDistributionPackageBuilder builder = new InMemoryDistributionPackageBuilder("name", new InMemDistributionContentSerializer(), new String[0], new String[0]);
-        DistributionPackage pkg = builder.createPackageForAdd(mock(ResourceResolver.class), new SimpleDistributionRequest(DistributionRequestType.ADD, false, "/test"));
+    public void testReadPackage() throws Exception {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        Map<String, Object> info = new HashMap<String, Object>();
+        info.put("test1", "value1");
+        DistributionPackageUtils.writeInfo(outputStream, info);
+        InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
+
+        DistributionPackage distPackage = builder.readPackage(resolver, inputStream);
+        DistributionPackageInfo outInfo = distPackage.getInfo();
+        assertThat(outInfo.get("test1", String.class), equalTo("value1"));
+    }
+    
+    @Test
+    public void testGetPackageInvalid() throws Exception {
+        String id = "invalid";
+        DistributionPackage distPackage = builder.getPackage(resolver, id);
+        assertThat(distPackage, nullValue());
+    }
+    
+    @Test
+    public void testInstallPackage() throws Exception {
+        SimpleDistributionRequest distributionRequest = new SimpleDistributionRequest(DistributionRequestType.ADD, false, "/test");
+        DistributionPackage pkg = builder.createPackage(resolver, distributionRequest);
         assertNotNull(pkg.createInputStream());
+        builder.installPackage(resolver, pkg);
+        verify(installHook).onPostAdd(Mockito.eq(resolver), Mockito.eq(pkg));
+    }
+    
+    @Test
+    public void testDeletePackage() throws Exception {
+        SimpleDistributionRequest distributionRequest = new SimpleDistributionRequest(DistributionRequestType.DELETE, false, "/test");
+        DistributionPackage pkg = builder.createPackage(resolver, distributionRequest);
+        builder.installPackage(resolver, pkg);
+        verify(installHook).onPreRemove(Mockito.eq(resolver), Mockito.eq(pkg));
     }
 
     private final class InMemDistributionContentSerializer implements DistributionContentSerializer {
diff --git a/src/test/java/org/apache/sling/distribution/packaging/impl/SimpleDistributionPackageTest.java b/src/test/java/org/apache/sling/distribution/packaging/impl/SimpleDistributionPackageTest.java
index 1730475..9e419bb 100644
--- a/src/test/java/org/apache/sling/distribution/packaging/impl/SimpleDistributionPackageTest.java
+++ b/src/test/java/org/apache/sling/distribution/packaging/impl/SimpleDistributionPackageTest.java
@@ -19,6 +19,8 @@
 package org.apache.sling.distribution.packaging.impl;
 
 import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.Arrays;
 
 import org.apache.commons.io.IOUtils;
@@ -27,27 +29,73 @@
 import org.apache.sling.distribution.SimpleDistributionRequest;
 import org.apache.sling.distribution.packaging.impl.SimpleDistributionPackage;
 import org.junit.Test;
+import org.mockito.Mockito;
 
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.nullValue;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 /**
  * Testcase for {@link SimpleDistributionPackage}
  */
 public class SimpleDistributionPackageTest {
 
+    private static final String DSTRPCK_DELETE = "DSTRPCK:DELETE|/abc,/c";
+
+    @Test
+    public void testInvalid() {
+        String id = "invalid";
+        SimpleDistributionPackage pkg = SimpleDistributionPackage.fromIdString(id, "");
+        assertThat(pkg, nullValue());
+    }
+    
+    @Test
+    public void testInvalid2() {
+        String id = "DSTRPCK:";
+        SimpleDistributionPackage pkg = SimpleDistributionPackage.fromIdString(id, "");
+        assertThat(pkg, nullValue());
+    }
+    
+    @Test
+    public void testInvalid3() {
+        String id = "DSTRPCK:a|b|c";
+        SimpleDistributionPackage pkg = SimpleDistributionPackage.fromIdString(id, "");
+        assertThat(pkg, nullValue());
+    }
+    
+    @Test
+    public void testFromStreamError() throws IOException {
+        InputStream stream = mock(InputStream.class);
+        when(stream.read(Mockito.any(byte[].class), Mockito.eq(0), Mockito.anyInt())).thenThrow(new IOException("Expected"));
+        SimpleDistributionPackage pkg = SimpleDistributionPackage.fromStream(stream, "ADD");
+        assertThat(pkg, nullValue());
+    }
+    
     @Test
     public void testCreatedAndReadPackagesEquality() throws Exception {
-        DistributionRequest request = new SimpleDistributionRequest(DistributionRequestType.DELETE, "/abc");
+        DistributionRequest request = new SimpleDistributionRequest(DistributionRequestType.DELETE, "/abc", "/c");
         SimpleDistributionPackage createdPackage = new SimpleDistributionPackage(request, "VOID");
-        SimpleDistributionPackage readPackage = SimpleDistributionPackage.fromStream(new ByteArrayInputStream(("DSTRPCK:DELETE|/abc").getBytes()), "VOID");
+        assertThat(createdPackage.toString(), equalTo(DSTRPCK_DELETE));
+        // Just to run the code
+        createdPackage.acquire();
+        createdPackage.release();
+        createdPackage.close();
+        createdPackage.delete();
+        
+        SimpleDistributionPackage readPackage = SimpleDistributionPackage.fromStream(new ByteArrayInputStream(DSTRPCK_DELETE.getBytes()), "VOID");
         assertNotNull(readPackage);
+        assertEquals(DSTRPCK_DELETE.length(), readPackage.getSize());
         assertEquals(createdPackage.getType(), readPackage.getType());
         assertEquals(createdPackage.getInfo().getRequestType(), readPackage.getInfo().getRequestType());
         assertEquals(Arrays.toString(createdPackage.getInfo().getPaths()), Arrays.toString(readPackage.getInfo().getPaths()));
         assertEquals(createdPackage.getId(), readPackage.getId());
         assertTrue(IOUtils.contentEquals(createdPackage.createInputStream(), readPackage.createInputStream()));
+        
     }
 
     @Test
diff --git a/src/test/java/org/apache/sling/distribution/packaging/impl/importer/LocalDistributionPackageImporterTest.java b/src/test/java/org/apache/sling/distribution/packaging/impl/importer/LocalDistributionPackageImporterTest.java
index d603237..99843db 100644
--- a/src/test/java/org/apache/sling/distribution/packaging/impl/importer/LocalDistributionPackageImporterTest.java
+++ b/src/test/java/org/apache/sling/distribution/packaging/impl/importer/LocalDistributionPackageImporterTest.java
@@ -32,8 +32,10 @@
 import org.apache.sling.distribution.packaging.DistributionPackage;
 import org.apache.sling.distribution.packaging.DistributionPackageBuilder;
 import org.apache.sling.distribution.packaging.DistributionPackageInfo;
+import org.apache.sling.distribution.packaging.PackageInstallHook;
 import org.apache.sling.distribution.packaging.impl.DistributionPackageUtils;
 import org.apache.sling.distribution.packaging.impl.FileDistributionPackageBuilder;
+import org.apache.sling.distribution.packaging.impl.NoopPackageInstallHook;
 import org.apache.sling.distribution.serialization.impl.vlt.FileVaultContentSerializer;
 import org.apache.sling.testing.mock.sling.ResourceResolverType;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
@@ -92,8 +94,9 @@
                 false
         );
 
+        PackageInstallHook postInstallHook = new NoopPackageInstallHook();
         DistributionPackageBuilder builder =
-                new FileDistributionPackageBuilder(DistributionRequestType.ADD.name(), vaultSerializer, null, null, null, null);
+                new FileDistributionPackageBuilder(DistributionRequestType.ADD.name(), vaultSerializer, null, null, null, null, postInstallHook);
 
         ResourceResolver resourceResolver = slingContext.resourceResolver();
 
diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml
new file mode 100644
index 0000000..474e4ca
--- /dev/null
+++ b/src/test/resources/logback.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<configuration>
+  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <pattern>%date %level [%thread] %logger{10} [%file : %line] %msg - %mdc %n</pattern>
+    </encoder>
+  </appender>
+
+  <root level="info">
+    <appender-ref ref="console"/>
+  </root>
+</configuration>