You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by kw...@apache.org on 2021/03/16 15:37:46 UTC

[jackrabbit-filevault] branch master updated: JCRVLT-511 add package task options (#128)

This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git


The following commit(s) were added to refs/heads/master by this push:
     new d3c81ba  JCRVLT-511 add package task options (#128)
d3c81ba is described below

commit d3c81ba1b3fb36eb52824b8d4ab1c9506eb5bbbb
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Tue Mar 16 16:37:37 2021 +0100

    JCRVLT-511 add package task options (#128)
---
 .../jackrabbit/vault/fs/io/ImportOptions.java      | 105 +++++++++++++++
 .../jackrabbit/vault/fs/io/package-info.java       |   2 +-
 .../vault/packaging/registry/PackageTask.java      |   7 +
 .../packaging/registry/PackageTaskBuilder.java     |  10 +-
 .../{package-info.java => PackageTaskOptions.java} |   5 +-
 .../registry/impl/ExecutionPlanBuilderImpl.java    |  53 +++++---
 .../packaging/registry/impl/PackageTaskImpl.java   |  73 ++++++++--
 .../impl/PackageTaskOptionsSerializer.java         | 149 +++++++++++++++++++++
 .../vault/packaging/registry/package-info.java     |   2 +-
 .../taskoption/ImportOptionsPackageTaskOption.java |  66 +++++++++
 .../impl/ExecutionPlanBuilderImplTest.java         |  78 +++++++++++
 .../registry/impl/MockPackageRegistry.java         |   5 +-
 12 files changed, 526 insertions(+), 29 deletions(-)

diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/ImportOptions.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/ImportOptions.java
index bff88c1..5ab14ee 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/ImportOptions.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/ImportOptions.java
@@ -405,4 +405,109 @@ public class ImportOptions {
     public void setDependencyHandling(DependencyHandling dependencyHandling) {
         this.dependencyHandling = dependencyHandling;
     }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((acHandling == null) ? 0 : acHandling.hashCode());
+        result = prime * result + autoSave;
+        result = prime * result + ((cndPattern == null) ? 0 : cndPattern.hashCode());
+        result = prime * result + ((cugHandling == null) ? 0 : cugHandling.hashCode());
+        result = prime * result + ((dependencyHandling == null) ? 0 : dependencyHandling.hashCode());
+        result = prime * result + (dryRun ? 1231 : 1237);
+        result = prime * result + ((filter == null) ? 0 : filter.hashCode());
+        result = prime * result + ((hookClassLoader == null) ? 0 : hookClassLoader.hashCode());
+        result = prime * result + ((importMode == null) ? 0 : importMode.hashCode());
+        result = prime * result + ((listener == null) ? 0 : listener.hashCode());
+        result = prime * result + (nonRecursive ? 1231 : 1237);
+        result = prime * result + ((patchDirectory == null) ? 0 : patchDirectory.hashCode());
+        result = prime * result + (patchKeepInRepo ? 1231 : 1237);
+        result = prime * result + ((patchParentPath == null) ? 0 : patchParentPath.hashCode());
+        result = prime * result + ((pathMapping == null) ? 0 : pathMapping.hashCode());
+        result = prime * result + (strict ? 1231 : 1237);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        ImportOptions other = (ImportOptions) obj;
+        if (acHandling != other.acHandling)
+            return false;
+        if (autoSave != other.autoSave)
+            return false;
+        if (cndPattern == null) {
+            if (other.cndPattern != null)
+                return false;
+        } else if (!cndPattern.pattern().equals(other.cndPattern.pattern()))
+            return false;
+        if (cugHandling != other.cugHandling)
+            return false;
+        if (dependencyHandling != other.dependencyHandling)
+            return false;
+        if (dryRun != other.dryRun)
+            return false;
+        if (filter == null) {
+            if (other.filter != null)
+                return false;
+        } else if (!filter.equals(other.filter))
+            return false;
+        if (hookClassLoader == null) {
+            if (other.hookClassLoader != null)
+                return false;
+        } else if (!hookClassLoader.equals(other.hookClassLoader))
+            return false;
+        if (importMode != other.importMode)
+            return false;
+        if (listener == null) {
+            if (other.listener != null)
+                return false;
+        } else if (!listener.equals(other.listener))
+            return false;
+        if (nonRecursive != other.nonRecursive)
+            return false;
+        if (patchDirectory == null) {
+            if (other.patchDirectory != null)
+                return false;
+        } else if (!patchDirectory.equals(other.patchDirectory))
+            return false;
+        if (patchKeepInRepo != other.patchKeepInRepo)
+            return false;
+        if (patchParentPath == null) {
+            if (other.patchParentPath != null)
+                return false;
+        } else if (!patchParentPath.equals(other.patchParentPath))
+            return false;
+        if (pathMapping == null) {
+            if (other.pathMapping != null)
+                return false;
+        } else if (!pathMapping.equals(other.pathMapping))
+            return false;
+        if (strict != other.strict)
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "ImportOptions [strict=" + strict + ", " + (listener != null ? "listener=" + listener + ", " : "")
+                + (patchParentPath != null ? "patchParentPath=" + patchParentPath + ", " : "")
+                + (patchDirectory != null ? "patchDirectory=" + patchDirectory + ", " : "") + "patchKeepInRepo=" + patchKeepInRepo
+                + ", nonRecursive=" + nonRecursive + ", dryRun=" + dryRun + ", autoSave=" + autoSave + ", "
+                + (acHandling != null ? "acHandling=" + acHandling + ", " : "")
+                + (cugHandling != null ? "cugHandling=" + cugHandling + ", " : "")
+                + (importMode != null ? "importMode=" + importMode + ", " : "")
+                + (cndPattern != null ? "cndPattern=" + cndPattern + ", " : "") + (filter != null ? "filter=" + filter + ", " : "")
+                + (hookClassLoader != null ? "hookClassLoader=" + hookClassLoader + ", " : "")
+                + (pathMapping != null ? "pathMapping=" + pathMapping + ", " : "")
+                + (dependencyHandling != null ? "dependencyHandling=" + dependencyHandling : "") + "]";
+    }
+    
+    
 }
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/package-info.java
index 2145118..ac05198 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/package-info.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-@Version("2.9.1")
+@Version("2.9.2")
 package org.apache.jackrabbit.vault.fs.io;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/PackageTask.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/PackageTask.java
index 15c0b31..42396c4 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/PackageTask.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/PackageTask.java
@@ -90,6 +90,13 @@ public interface PackageTask {
     Type getType();
 
     /**
+     * Returns the task optional options.
+     * @return the task options (may be null).
+     */
+    @Nullable
+    PackageTaskOptions getOptions();
+
+    /**
      * Returns the task state
      * @return the task state
      */
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/PackageTaskBuilder.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/PackageTaskBuilder.java
index 29d1666..925b951 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/PackageTaskBuilder.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/PackageTaskBuilder.java
@@ -37,9 +37,17 @@ public interface PackageTaskBuilder {
     /**
      * Sets the type of this task
      * @param type the type
-     * @return this.
+     * @return the parent execution plan builder.
      */
     @NotNull
     ExecutionPlanBuilder with(@NotNull PackageTask.Type type);
+    
+    /**
+     * Set the optional options for the package task
+     * @param options the options
+     * @return this.
+     */
+    @NotNull
+    PackageTaskBuilder withOptions(@NotNull PackageTaskOptions options);
 
 }
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/PackageTaskOptions.java
similarity index 94%
copy from vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/package-info.java
copy to vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/PackageTaskOptions.java
index c96debb..0098d76 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/PackageTaskOptions.java
@@ -14,5 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@org.osgi.annotation.versioning.Version("1.3.1")
 package org.apache.jackrabbit.vault.packaging.registry;
+
+public interface PackageTaskOptions {
+
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/ExecutionPlanBuilderImpl.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/ExecutionPlanBuilderImpl.java
index 5fdca4e..8888aea 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/ExecutionPlanBuilderImpl.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/ExecutionPlanBuilderImpl.java
@@ -48,11 +48,13 @@ import org.apache.jackrabbit.vault.packaging.registry.ExecutionPlanBuilder;
 import org.apache.jackrabbit.vault.packaging.registry.PackageRegistry;
 import org.apache.jackrabbit.vault.packaging.registry.PackageTask;
 import org.apache.jackrabbit.vault.packaging.registry.PackageTaskBuilder;
+import org.apache.jackrabbit.vault.packaging.registry.PackageTaskOptions;
 import org.apache.jackrabbit.vault.packaging.registry.RegisteredPackage;
 import org.apache.jackrabbit.vault.util.RejectingEntityResolver;
 import org.apache.jackrabbit.vault.util.xml.serialize.FormattingXmlStreamWriter;
 import org.apache.jackrabbit.vault.util.xml.serialize.OutputFormat;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
@@ -71,7 +73,7 @@ public class ExecutionPlanBuilderImpl implements ExecutionPlanBuilder {
      */
     private static final Logger log = LoggerFactory.getLogger(ExecutionPlanBuilderImpl.class);
 
-    private final static String ATTR_VERSION = "version";
+    private static final String ATTR_VERSION = "version";
     private static final String TAG_EXECUTION_PLAN = "executionPlan";
     private static final String TAG_TASK = "task";
     private static final String ATTR_CMD = "cmd";
@@ -81,10 +83,12 @@ public class ExecutionPlanBuilderImpl implements ExecutionPlanBuilder {
 
     protected double version = SUPPORTED_VERSION;
 
-    private final List<TaskBuilder> tasks = new LinkedList<TaskBuilder>();
+    private final List<TaskBuilder> tasks = new LinkedList<>();
 
     private final PackageRegistry registry;
 
+    private final PackageTaskOptionsSerializer optionsSerializer;
+
     private Session session;
 
     private ProgressTrackerListener listener;
@@ -95,6 +99,7 @@ public class ExecutionPlanBuilderImpl implements ExecutionPlanBuilder {
 
     ExecutionPlanBuilderImpl(PackageRegistry registry) {
         this.registry = registry;
+        optionsSerializer = new PackageTaskOptionsSerializer();
     }
 
     @NotNull
@@ -109,6 +114,7 @@ public class ExecutionPlanBuilderImpl implements ExecutionPlanBuilder {
                 writer.writeStartElement(TAG_TASK);
                 writer.writeAttribute(ATTR_CMD, task.getType().name().toLowerCase());
                 writer.writeAttribute(ATTR_PACKAGE_ID, task.getPackageId().toString());
+                optionsSerializer.save(writer, task.getOptions());
                 writer.writeEndElement();
             }
             writer.writeEndElement();
@@ -168,7 +174,13 @@ public class ExecutionPlanBuilderImpl implements ExecutionPlanBuilder {
     private void readTask(Element elem) throws IOException {
         PackageTask.Type type = PackageTask.Type.valueOf(elem.getAttribute(ATTR_CMD).toUpperCase());
         PackageId id = PackageId.fromString(elem.getAttribute(ATTR_PACKAGE_ID));
-        addTask().with(id).with(type);
+        
+        PackageTaskBuilder packageTaskBuilder = addTask().with(id);
+        PackageTaskOptions options = optionsSerializer.load(elem);
+        if (options != null) {
+            packageTaskBuilder.withOptions(options);
+        }
+        packageTaskBuilder.with(type);
     }
 
     @NotNull
@@ -197,10 +209,10 @@ public class ExecutionPlanBuilderImpl implements ExecutionPlanBuilder {
     @NotNull
     @Override
     public ExecutionPlanBuilder validate() throws IOException, PackageException {
-        Map<PackageId, PackageTask> installTasks = new HashMap<PackageId, PackageTask>();
-        Map<PackageId, PackageTask> uninstallTasks = new HashMap<PackageId, PackageTask>();
-        Map<PackageId, PackageTask> removeTasks = new HashMap<PackageId, PackageTask>();
-        List<PackageTask> packageTasks = new ArrayList<PackageTask>(tasks.size());
+        Map<PackageId, PackageTask> installTasks = new HashMap<>();
+        Map<PackageId, PackageTask> uninstallTasks = new HashMap<>();
+        Map<PackageId, PackageTask> removeTasks = new HashMap<>();
+        List<PackageTask> packageTasks = new ArrayList<>(tasks.size());
         for (TaskBuilder task: tasks) {
             if (task.id == null || task.type == null) {
                 throw new PackageException("task builder must have package id and type defined.");
@@ -208,7 +220,7 @@ public class ExecutionPlanBuilderImpl implements ExecutionPlanBuilder {
             if (!registry.contains(task.id)) {
                 throw new NoSuchPackageException("No such package: " + task.id);
             }
-            PackageTaskImpl pTask = new PackageTaskImpl(task.id, task.type);
+            PackageTaskImpl pTask = new PackageTaskImpl(task.id, task.type, task.option);
             // very simple task resolution: uninstall -> remove -> install/extract
             switch (task.type) {
                 case INSTALL:
@@ -225,14 +237,15 @@ public class ExecutionPlanBuilderImpl implements ExecutionPlanBuilder {
         }
 
         for (PackageId id: uninstallTasks.keySet().toArray(new PackageId[uninstallTasks.size()])) {
-            resolveUninstall(id, packageTasks, uninstallTasks, new HashSet<PackageId>());
+            resolveUninstall(id, packageTasks, uninstallTasks, new HashSet<>(), uninstallTasks.get(id).getOptions());
         }
 
         // todo: validate remove
         packageTasks.addAll(removeTasks.values());
 
         for (PackageId id: installTasks.keySet().toArray(new PackageId[installTasks.size()])) {
-            resolveInstall(id, packageTasks, installTasks, new HashSet<PackageId>(), installTasks.get(id).getType());
+            PackageTask task = installTasks.get(id);
+            resolveInstall(id, packageTasks, installTasks, new HashSet<>(), task.getType(), task.getOptions());
         }
 
         for (PackageTask task: packageTasks) {
@@ -243,7 +256,7 @@ public class ExecutionPlanBuilderImpl implements ExecutionPlanBuilder {
         return this;
     }
 
-    private void resolveInstall(PackageId id, List<PackageTask> packageTasks, Map<PackageId, PackageTask> installTasks, Set<PackageId> resolved, PackageTask.Type type) throws IOException, PackageException {
+    private void resolveInstall(PackageId id, List<PackageTask> packageTasks, Map<PackageId, PackageTask> installTasks, Set<PackageId> resolved, PackageTask.Type type, @Nullable PackageTaskOptions option) throws IOException, PackageException {
         if (resolved.contains(id)) {
             throw new CyclicDependencyException("Package has cyclic dependencies: " + id);
         }
@@ -263,7 +276,7 @@ public class ExecutionPlanBuilderImpl implements ExecutionPlanBuilder {
                     continue;
                 }
             }
-            resolveInstall(depId, packageTasks, installTasks, resolved, type);
+            resolveInstall(depId, packageTasks, installTasks, resolved, type, option);
         }
         PackageTask task = installTasks.get(id);
         if (task == PackageTaskImpl.MARKER) {
@@ -274,7 +287,7 @@ public class ExecutionPlanBuilderImpl implements ExecutionPlanBuilder {
             if (task == null) {
                 // package is not registered in plan, but need to be installed
                 // due to dependency
-                task = new PackageTaskImpl(id, type);
+                task = new PackageTaskImpl(id, type, option);
             }
             packageTasks.add(task);
         }
@@ -282,7 +295,7 @@ public class ExecutionPlanBuilderImpl implements ExecutionPlanBuilder {
         installTasks.put(id, PackageTaskImpl.MARKER);
     }
 
-    private void resolveUninstall(PackageId id, List<PackageTask> packageTasks, Map<PackageId, PackageTask> uninstallTasks, Set<PackageId> resolved) throws IOException, PackageException {
+    private void resolveUninstall(PackageId id, List<PackageTask> packageTasks, Map<PackageId, PackageTask> uninstallTasks, Set<PackageId> resolved, @Nullable PackageTaskOptions option) throws IOException, PackageException {
         if (resolved.contains(id)) {
             throw new CyclicDependencyException("Package has cyclic dependencies: " + id);
         }
@@ -298,7 +311,7 @@ public class ExecutionPlanBuilderImpl implements ExecutionPlanBuilder {
                     continue;
                 }
             }
-            resolveUninstall(depId, packageTasks, uninstallTasks, resolved);
+            resolveUninstall(depId, packageTasks, uninstallTasks, resolved, option);
         }
         PackageTask task = uninstallTasks.get(id);
         if (task == PackageTaskImpl.MARKER) {
@@ -307,7 +320,7 @@ public class ExecutionPlanBuilderImpl implements ExecutionPlanBuilder {
         }
         if (task == null) {
             // package is not registered in plan, but need to be installed due to dependency
-            task = new PackageTaskImpl(id, PackageTask.Type.UNINSTALL);
+            task = new PackageTaskImpl(id, PackageTask.Type.UNINSTALL, option);
         }
         packageTasks.add(task);
         // mark as processed
@@ -334,6 +347,7 @@ public class ExecutionPlanBuilderImpl implements ExecutionPlanBuilder {
     private class TaskBuilder implements PackageTaskBuilder {
         private PackageId id;
         private PackageTask.Type type;
+        private PackageTaskOptions option;
 
         public PackageTaskBuilder with(@NotNull PackageId id) {
             this.id = id;
@@ -346,6 +360,13 @@ public class ExecutionPlanBuilderImpl implements ExecutionPlanBuilder {
             this.type = type;
             return ExecutionPlanBuilderImpl.this;
         }
+
+        @Override
+        @NotNull
+        public PackageTaskBuilder withOptions(@NotNull PackageTaskOptions option) {
+            this.option = option;
+            return this;
+        }
     }
 
     @Override
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/PackageTaskImpl.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/PackageTaskImpl.java
index ed1fdae..391910c 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/PackageTaskImpl.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/PackageTaskImpl.java
@@ -25,7 +25,9 @@ import org.apache.jackrabbit.vault.packaging.PackageException;
 import org.apache.jackrabbit.vault.packaging.PackageId;
 import org.apache.jackrabbit.vault.packaging.registry.PackageRegistry;
 import org.apache.jackrabbit.vault.packaging.registry.PackageTask;
+import org.apache.jackrabbit.vault.packaging.registry.PackageTaskOptions;
 import org.apache.jackrabbit.vault.packaging.registry.RegisteredPackage;
+import org.apache.jackrabbit.vault.packaging.registry.taskoption.ImportOptionsPackageTaskOption;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
@@ -41,19 +43,22 @@ public class PackageTaskImpl implements PackageTask {
      */
     private static final Logger log = LoggerFactory.getLogger(PackageTaskImpl.class);
 
-    final static PackageTaskImpl MARKER = new PackageTaskImpl(new PackageId("", "" ,""), Type.INSTALL);
+    final static PackageTaskImpl MARKER = new PackageTaskImpl(new PackageId("", "" ,""), Type.INSTALL, null);
 
     private final PackageId id;
 
     private final Type type;
 
-    private State state = State.NEW;
+    private final PackageTaskOptions options;
+
+    State state = State.NEW;
 
     private Throwable error;
 
-    PackageTaskImpl(@NotNull PackageId id, @NotNull Type type) {
+    PackageTaskImpl(@NotNull PackageId id, @NotNull Type type, @Nullable PackageTaskOptions options) {
         this.id = id;
         this.type = type;
+        this.options = options;
     }
 
     @NotNull
@@ -68,6 +73,12 @@ public class PackageTaskImpl implements PackageTask {
         return type;
     }
 
+    @Nullable
+    @Override
+    public PackageTaskOptions getOptions() {
+        return options;
+    }
+
     @NotNull
     @Override
     public State getState() {
@@ -82,10 +93,9 @@ public class PackageTaskImpl implements PackageTask {
 
     @Override
     public String toString() {
-        return "PackageTaskImpl{" + "id=" + id +
-                ", type=" + type +
-                ", state=" + state +
-                '}';
+        return "PackageTaskImpl [" + (id != null ? "id=" + id + ", " : "") + (type != null ? "type=" + type + ", " : "")
+                + (options != null ? "option=" + options + ", " : "") + (state != null ? "state=" + state + ", " : "")
+                + (error != null ? "error=" + error : "") + "]";
     }
 
     void execute(ExecutionPlanImpl executionPlan) {
@@ -164,7 +174,12 @@ public class PackageTaskImpl implements PackageTask {
      * @throws PackageException if a package error occurs
      */
     private void doInstall(ExecutionPlanImpl plan, boolean extract) throws IOException, PackageException {
-        ImportOptions opts = new ImportOptions();
+        final ImportOptions opts;
+        if (options instanceof ImportOptionsPackageTaskOption) {
+            opts = ((ImportOptionsPackageTaskOption) options).getImportOptions().copy();
+        } else {
+            opts = new ImportOptions();
+        }
         opts.setListener(plan.getListener());
         // execution plan resolution already has resolved all dependencies, so there is no need to use best effort here.
         opts.setDependencyHandling(DependencyHandling.STRICT);
@@ -180,5 +195,47 @@ public class PackageTaskImpl implements PackageTask {
         }
     }
 
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((error == null) ? 0 : error.hashCode());
+        result = prime * result + ((id == null) ? 0 : id.hashCode());
+        result = prime * result + ((options == null) ? 0 : options.hashCode());
+        result = prime * result + ((state == null) ? 0 : state.hashCode());
+        result = prime * result + ((type == null) ? 0 : type.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        PackageTaskImpl other = (PackageTaskImpl) obj;
+        if (error == null) {
+            if (other.error != null)
+                return false;
+        } else if (!error.equals(other.error))
+            return false;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        if (options == null) {
+            if (other.options != null)
+                return false;
+        } else if (!options.equals(other.options))
+            return false;
+        if (state != other.state)
+            return false;
+        if (type != other.type)
+            return false;
+        return true;
+    }
 
 }
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/PackageTaskOptionsSerializer.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/PackageTaskOptionsSerializer.java
new file mode 100644
index 0000000..e6bbe80
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/PackageTaskOptionsSerializer.java
@@ -0,0 +1,149 @@
+/*
+ * 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.jackrabbit.vault.packaging.registry.impl;
+
+import java.util.function.Consumer;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.apache.jackrabbit.vault.fs.api.ImportMode;
+import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
+import org.apache.jackrabbit.vault.packaging.DependencyHandling;
+import org.apache.jackrabbit.vault.packaging.registry.PackageTaskOptions;
+import org.apache.jackrabbit.vault.packaging.registry.taskoption.ImportOptionsPackageTaskOption;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+public class PackageTaskOptionsSerializer {
+
+    private static final String TAG_OPTIONS = "options";
+    private static final String ATTR_TYPE = "type";
+    private static final String TAG_AC_HANDLING = "acHandling";
+    private static final String TAG_IS_STRICT = "isStrict";
+    private static final String TAG_AUTO_SAVE_THRESHOLD = "autoSaveThreshold";
+    private static final String TAG_DEPENDENCY_HANDLING = "dependencyHandling";
+    private static final String TAG_CUG_HANDLING = "cugHandling";
+    private static final String TAG_NON_RECURSIVE = "nonRecursive";
+    private static final String TAG_DRY_RUN = "dryRun";
+    private static final String TAG_IMPORT_MODE = "importMode";
+    
+    enum Type {
+        ImportOptions;
+
+        static Type fromClass(PackageTaskOptions options) {
+            if (options instanceof ImportOptionsPackageTaskOption) {
+                return ImportOptions;
+            } else {
+                throw new IllegalStateException("Unsupported task option class " + options.getClass());
+            }
+        }
+    }
+
+    public PackageTaskOptions load(Element element) {
+        Element childElement = getFirstElementByTagName(TAG_OPTIONS, element);
+        if (childElement == null) {
+            return null;
+        }
+        final PackageTaskOptions options;
+        switch (Type.valueOf(childElement.getAttribute(ATTR_TYPE))) {
+            case ImportOptions:
+                options = loadImportOptions(childElement);
+                break;
+            default:
+                throw new IllegalArgumentException("Wrong type used");
+        }
+        return options;
+    }
+
+    public void save(XMLStreamWriter writer, PackageTaskOptions options) throws XMLStreamException {
+        if (options == null) {
+            return;
+        }
+        writer.writeStartElement(TAG_OPTIONS);
+        Type type = Type.fromClass(options);
+        writer.writeAttribute(ATTR_TYPE, type.name());
+        switch (type) {
+            case ImportOptions:
+                saveImportOptions(writer, (ImportOptionsPackageTaskOption) options);
+                break;
+        }
+        writer.writeEndElement();
+    }
+
+    public void saveImportOptions(XMLStreamWriter writer, ImportOptionsPackageTaskOption options) throws XMLStreamException {
+        ImportOptions importOptions = options.getImportOptions();
+        writeOption(writer, TAG_IS_STRICT, Boolean.class, importOptions.isStrict());
+        writeOption(writer, TAG_AC_HANDLING, AccessControlHandling.class, importOptions.getAccessControlHandling());
+        writeOption(writer, TAG_CUG_HANDLING, AccessControlHandling.class, importOptions.getCugHandling());
+        writeOption(writer, TAG_AUTO_SAVE_THRESHOLD, Integer.class, importOptions.getAutoSaveThreshold());
+        writeOption(writer, TAG_DEPENDENCY_HANDLING, DependencyHandling.class, importOptions.getDependencyHandling());
+        writeOption(writer, TAG_NON_RECURSIVE, Boolean.class, importOptions.isNonRecursive());
+        writeOption(writer, TAG_DRY_RUN, Boolean.class, importOptions.isDryRun());
+        writeOption(writer, TAG_IMPORT_MODE, ImportMode.class, importOptions.getImportMode());
+    }
+
+    public ImportOptionsPackageTaskOption loadImportOptions(Element element) {
+        ImportOptions options = new ImportOptions();
+        readOption(element, TAG_IS_STRICT, Boolean.class, options::setStrict);
+        readOption(element, TAG_AC_HANDLING, AccessControlHandling.class, options::setAccessControlHandling);
+        readOption(element, TAG_CUG_HANDLING, AccessControlHandling.class, options::setCugHandling);
+        readOption(element, TAG_AUTO_SAVE_THRESHOLD, Integer.class, options::setAutoSaveThreshold);
+        readOption(element, TAG_DEPENDENCY_HANDLING, DependencyHandling.class, options::setDependencyHandling);
+        readOption(element, TAG_NON_RECURSIVE, Boolean.class, options::setNonRecursive);
+        readOption(element, TAG_DRY_RUN, Boolean.class,  options::setDryRun);
+        readOption(element, TAG_IMPORT_MODE, ImportMode.class,  options::setImportMode);
+        return new ImportOptionsPackageTaskOption(options);
+    }
+
+    private <T> void writeOption(XMLStreamWriter writer, String tagElement, Class<T> type, T value) throws XMLStreamException {
+        if (value != null) {
+            writer.writeStartElement(tagElement);
+            if (type.equals(Boolean.class)) {
+                writer.writeCharacters(Boolean.toString((Boolean)value));
+            } else if (type.isEnum()) {
+                writer.writeCharacters(((Enum<?>)value).name());
+            } else if (type.equals(Integer.class)) {
+                writer.writeCharacters(Integer.toString((Integer)value));
+            }
+            writer.writeEndElement();
+        }
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private <T> void readOption(Element element, String tagName, Class<T> type, Consumer<T> consumer) {
+        Element childElement = getFirstElementByTagName(tagName, element);
+        if (childElement != null) {
+            if (type.equals(Boolean.class)) {
+                consumer.accept((T)Boolean.valueOf(childElement.getTextContent()));
+            } else if (type.isEnum()) {
+                consumer.accept((T)Enum.valueOf((Class)type, childElement.getTextContent()));
+            } else if (type.equals(Integer.class)) {
+                consumer.accept((T)Integer.valueOf(childElement.getTextContent()));
+            }
+        }
+    }
+
+    private static final Element getFirstElementByTagName(String name, Element element) {
+        NodeList nodeList = element.getElementsByTagName(name);
+        if (nodeList.getLength() == 0) {
+            return null;
+        }
+        return (Element)nodeList.item(0);
+    }
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/package-info.java
index c96debb..a9f3979 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/package-info.java
@@ -14,5 +14,5 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@org.osgi.annotation.versioning.Version("1.3.1")
+@org.osgi.annotation.versioning.Version("1.4.0")
 package org.apache.jackrabbit.vault.packaging.registry;
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/taskoption/ImportOptionsPackageTaskOption.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/taskoption/ImportOptionsPackageTaskOption.java
new file mode 100644
index 0000000..114d9bc
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/taskoption/ImportOptionsPackageTaskOption.java
@@ -0,0 +1,66 @@
+/*
+ * 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.jackrabbit.vault.packaging.registry.taskoption;
+
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
+import org.apache.jackrabbit.vault.packaging.registry.PackageTaskOptions;
+
+public class ImportOptionsPackageTaskOption implements PackageTaskOptions {
+
+    private final ImportOptions importOptions;
+
+    public ImportOptionsPackageTaskOption(ImportOptions importOptions) {
+        super();
+        this.importOptions = importOptions;
+    }
+
+    public ImportOptions getImportOptions() {
+        return importOptions;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((importOptions == null) ? 0 : importOptions.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        ImportOptionsPackageTaskOption other = (ImportOptionsPackageTaskOption) obj;
+        if (importOptions == null) {
+            if (other.importOptions != null)
+                return false;
+        } else if (!importOptions.equals(other.importOptions))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "ImportOptionsPackageTaskOption [" + (importOptions != null ? "importOptions=" + importOptions : "") + "]";
+    }
+
+    
+}
diff --git a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/registry/impl/ExecutionPlanBuilderImplTest.java b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/registry/impl/ExecutionPlanBuilderImplTest.java
new file mode 100644
index 0000000..e5a19c1
--- /dev/null
+++ b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/registry/impl/ExecutionPlanBuilderImplTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.jackrabbit.vault.packaging.registry.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.vault.fs.api.ImportMode;
+import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
+import org.apache.jackrabbit.vault.packaging.PackageException;
+import org.apache.jackrabbit.vault.packaging.registry.ExecutionPlan;
+import org.apache.jackrabbit.vault.packaging.registry.PackageTask;
+import org.apache.jackrabbit.vault.packaging.registry.PackageTask.State;
+import org.apache.jackrabbit.vault.packaging.registry.PackageTask.Type;
+import org.apache.jackrabbit.vault.packaging.registry.PackageTaskOptions;
+import org.apache.jackrabbit.vault.packaging.registry.taskoption.ImportOptionsPackageTaskOption;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ExecutionPlanBuilderImplTest {
+
+    private ExecutionPlanBuilderImpl builder;
+
+    @Mock
+    private Session session;
+    @Before
+    public void setUp() {
+        builder = new ExecutionPlanBuilderImpl(new MockPackageRegistry(MockPackageRegistry.NEW_PACKAGE_ID));
+    }
+
+    @Test
+    public void testSaveAndLoad() throws IOException, PackageException {
+        ImportOptions importOptions = new ImportOptions();
+        importOptions.setStrict(true);
+        importOptions.setAccessControlHandling(AccessControlHandling.MERGE_PRESERVE);
+        importOptions.setAutoSaveThreshold(123);
+        importOptions.setCugHandling(AccessControlHandling.CLEAR);
+        importOptions.setImportMode(ImportMode.UPDATE);
+        importOptions.setDryRun(true);
+        importOptions.setNonRecursive(true);
+        PackageTaskOptions options = new ImportOptionsPackageTaskOption(importOptions);
+        builder.addTask().with(MockPackageRegistry.NEW_PACKAGE_ID).withOptions(options).with(PackageTask.Type.INSTALL);
+        
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        builder.save(out);
+        builder = new ExecutionPlanBuilderImpl(new MockPackageRegistry(MockPackageRegistry.NEW_PACKAGE_ID));
+        builder.load(new ByteArrayInputStream(out.toByteArray()));
+        builder.with(session);
+        ExecutionPlan plan = builder.execute();
+        PackageTaskImpl expectedTask = new PackageTaskImpl(MockPackageRegistry.NEW_PACKAGE_ID, Type.INSTALL, options);
+        expectedTask.state = State.COMPLETED;
+        MatcherAssert.assertThat(plan.getTasks(), Matchers.contains(expectedTask));
+    }
+}
diff --git a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/registry/impl/MockPackageRegistry.java b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/registry/impl/MockPackageRegistry.java
index fa01eb1..9ac5795 100644
--- a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/registry/impl/MockPackageRegistry.java
+++ b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/registry/impl/MockPackageRegistry.java
@@ -118,7 +118,10 @@ public final class MockPackageRegistry implements PackageRegistry {
     public @NotNull DependencyReport analyzeDependencies(@NotNull PackageId id, boolean onlyInstalled)
             throws IOException, NoSuchPackageException {
         if (containedPackageIdsAndDependencies.containsKey(id)) {
-            return Mockito.mock(DependencyReport.class);
+            DependencyReport report = Mockito.mock(DependencyReport.class);
+            Mockito.when(report.getUnresolvedDependencies()).thenReturn(new Dependency[0]);
+            Mockito.when(report.getResolvedDependencies()).thenReturn(new PackageId[0]);
+            return report;
         } else {
             throw new NoSuchPackageException("Could not find package with id " + id);
         }