You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by tr...@apache.org on 2018/07/24 14:32:13 UTC

svn commit: r1836554 [1/2] - in /jackrabbit/commons/filevault/trunk/vault-core: ./ src/main/java/org/apache/jackrabbit/vault/packaging/impl/ src/main/java/org/apache/jackrabbit/vault/packaging/registry/ src/main/java/org/apache/jackrabbit/vault/packagi...

Author: tripod
Date: Tue Jul 24 14:32:12 2018
New Revision: 1836554

URL: http://svn.apache.org/viewvc?rev=1836554&view=rev
Log:
JCRVLT-180 implement filesystem based persistence layer

Added:
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/AbstractPackageRegistry.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSInstallState.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSPackageRegistry.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSPackageStatus.java
      - copied, changed from r1833974, jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/package-info.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSRegisteredPackage.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/InternalPackageRegistry.java
    jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestFSInstallState.java
    jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestFSPackageRegistry.java
Modified:
    jackrabbit/commons/filevault/trunk/vault-core/pom.xml
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageManagerImpl.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/SubPackageExportProcessor.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/ExecutionPlanBuilderImpl.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/JcrPackageRegistry.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/PackageTaskImpl.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/package-info.java
    jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/IntegrationTestBase.java
    jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestPackageInstall.java
    jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestSubPackages.java

Modified: jackrabbit/commons/filevault/trunk/vault-core/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/pom.xml?rev=1836554&r1=1836553&r2=1836554&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/pom.xml (original)
+++ jackrabbit/commons/filevault/trunk/vault-core/pom.xml Tue Jul 24 14:32:12 2018
@@ -148,6 +148,13 @@
             <artifactId>jcr</artifactId>
             <scope>provided</scope>
         </dependency>
+        
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <version>4.3.0</version>
+            <scope>provided</scope>
+        </dependency>
 
         <!-- SLF4j / Log4j -->
         <dependency>

Modified: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageManagerImpl.java?rev=1836554&r1=1836553&r2=1836554&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageManagerImpl.java (original)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageManagerImpl.java Tue Jul 24 14:32:12 2018
@@ -58,6 +58,7 @@ import org.apache.jackrabbit.vault.packa
 import org.apache.jackrabbit.vault.packaging.VaultPackage;
 import org.apache.jackrabbit.vault.packaging.events.PackageEvent;
 import org.apache.jackrabbit.vault.packaging.events.impl.PackageEventDispatcher;
+import org.apache.jackrabbit.vault.packaging.registry.PackageRegistry;
 import org.apache.jackrabbit.vault.packaging.registry.impl.JcrPackageRegistry;
 import org.apache.jackrabbit.vault.packaging.registry.impl.JcrRegisteredPackage;
 import org.apache.jackrabbit.vault.util.JcrConstants;
@@ -102,7 +103,11 @@ public class JcrPackageManagerImpl exten
         return new RepositoryException(e);
     }
 
-    public JcrPackageRegistry getRegistry() {
+    public PackageRegistry getRegistry() {
+        return registry;
+    }
+    
+    public JcrPackageRegistry getInternalRegistry() {
         return registry;
     }
 

Modified: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/SubPackageExportProcessor.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/SubPackageExportProcessor.java?rev=1836554&r1=1836553&r2=1836554&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/SubPackageExportProcessor.java (original)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/SubPackageExportProcessor.java Tue Jul 24 14:32:12 2018
@@ -73,7 +73,7 @@ public class SubPackageExportProcessor i
                 if (Text.isDescendantOrEqual(DEFAULT_PACKAGE_ROOT_PATH, nodePath)) {
                     continue;
                 }
-                String etcPath = DEFAULT_PACKAGE_ROOT_PATH + mgr.getRegistry().getRelativeInstallationPath(pkg.getKey()) + ".zip";
+                String etcPath = DEFAULT_PACKAGE_ROOT_PATH + mgr.getInternalRegistry().getRelativeInstallationPath(pkg.getKey()) + ".zip";
                 etcPath = Text.getRelativeParent(etcPath, 1);
 
                 // define a workspace filter for the package at the real location
@@ -116,7 +116,7 @@ public class SubPackageExportProcessor i
             }
         }
         // now also get the packages from the primary root
-        WorkspaceFilter filter = originalFilter.translate(new SimplePathMapping(DEFAULT_PACKAGE_ROOT_PATH, mgr.getRegistry().getPackRootPaths()[0]));
+        WorkspaceFilter filter = originalFilter.translate(new SimplePathMapping(DEFAULT_PACKAGE_ROOT_PATH, mgr.getInternalRegistry().getPackRootPaths()[0]));
         for (JcrPackage pkg: mgr.listPackages(filter)) {
             if (pkg.isValid() && pkg.getSize() > 0) {
                 subPackages.put(pkg.getDefinition().getId(), pkg.getNode().getPath());
@@ -128,7 +128,7 @@ public class SubPackageExportProcessor i
             Iterator<PathFilterSet> iter = newFilter.getFilterSets().iterator();
             while (iter.hasNext()) {
                 PathFilterSet set = iter.next();
-                for (String root : mgr.getRegistry().getPackRootPaths()) {
+                for (String root : mgr.getInternalRegistry().getPackRootPaths()) {
                     if (Text.isDescendantOrEqual(root, set.getRoot())) {
                         iter.remove();
                         break;
@@ -138,7 +138,7 @@ public class SubPackageExportProcessor i
             iter = newFilter.getPropertyFilterSets().iterator();
             while (iter.hasNext()) {
                 PathFilterSet set = iter.next();
-                for (String root : mgr.getRegistry().getPackRootPaths()) {
+                for (String root : mgr.getInternalRegistry().getPackRootPaths()) {
                     if (Text.isDescendantOrEqual(root, set.getRoot())) {
                         iter.remove();
                         break;
@@ -148,7 +148,7 @@ public class SubPackageExportProcessor i
 
             // re-add all the packages in /etc/packages
             for (Map.Entry<PackageId, String> pkg : subPackages.entrySet()) {
-                String path = DEFAULT_PACKAGE_ROOT_PATH + mgr.getRegistry().getRelativeInstallationPath(pkg.getKey()) + ".zip";
+                String path = DEFAULT_PACKAGE_ROOT_PATH + mgr.getInternalRegistry().getRelativeInstallationPath(pkg.getKey()) + ".zip";
                 newFilter.add(new PathFilterSet(path));
             }
 

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/AbstractPackageRegistry.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/AbstractPackageRegistry.java?rev=1836554&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/AbstractPackageRegistry.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/AbstractPackageRegistry.java Tue Jul 24 14:32:12 2018
@@ -0,0 +1,218 @@
+/*
+ * 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.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import javax.annotation.Nonnull;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
+import org.apache.jackrabbit.vault.packaging.Dependency;
+import org.apache.jackrabbit.vault.packaging.NoSuchPackageException;
+import org.apache.jackrabbit.vault.packaging.PackageException;
+import org.apache.jackrabbit.vault.packaging.PackageExistsException;
+import org.apache.jackrabbit.vault.packaging.PackageId;
+import org.apache.jackrabbit.vault.packaging.registry.DependencyReport;
+import org.apache.jackrabbit.vault.packaging.registry.ExecutionPlanBuilder;
+import org.apache.jackrabbit.vault.packaging.registry.PackageRegistry;
+import org.apache.jackrabbit.vault.packaging.registry.RegisteredPackage;
+
+/**
+ * Abstraction for shared methods of PackageRegistry & InternalPackageRegistry implementations
+ */
+public abstract class AbstractPackageRegistry implements PackageRegistry, InternalPackageRegistry {
+
+    /**
+     * default root path for packages
+     */
+    public static final String DEFAULT_PACKAGE_ROOT_PATH = "/etc/packages";
+
+    /**
+     * Archive root path for packages
+     */
+    public final static String ARCHIVE_PACKAGE_ROOT_PATH = "/jcr_root" + DEFAULT_PACKAGE_ROOT_PATH;
+    
+    /**
+     * default root path prefix for packages
+     */
+    public static final String DEFAULT_PACKAGE_ROOT_PATH_PREFIX = DEFAULT_PACKAGE_ROOT_PATH + "/";
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public abstract void installPackage(Session session, RegisteredPackage pkg, ImportOptions opts, boolean extract)
+            throws IOException, PackageException;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public abstract void uninstallPackage(Session session, RegisteredPackage pkg, ImportOptions opts)
+            throws IOException, PackageException;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public abstract boolean contains(PackageId id) throws IOException;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public abstract Set<PackageId> packages() throws IOException;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public abstract RegisteredPackage open(PackageId id) throws IOException;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public abstract PackageId register(InputStream in, boolean replace) throws IOException, PackageExistsException;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public abstract PackageId register(File file, boolean replace) throws IOException, PackageExistsException;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public abstract PackageId registerExternal(File file, boolean replace) throws IOException, PackageExistsException;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public abstract void remove(PackageId id) throws IOException, NoSuchPackageException;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Nonnull
+    @Override
+    public DependencyReport analyzeDependencies(@Nonnull PackageId id, boolean onlyInstalled) throws IOException, NoSuchPackageException {
+        List<Dependency> unresolved = new LinkedList<Dependency>();
+        List<PackageId> resolved = new LinkedList<PackageId>();
+        try (RegisteredPackage pkg = open(id)) {
+            if (pkg == null) {
+                throw new NoSuchPackageException().setId(id);
+            }
+            //noinspection resource
+            for (Dependency dep : pkg.getPackage().getDependencies()) {
+                PackageId resolvedId = resolve(dep, onlyInstalled);
+                if (resolvedId == null) {
+                    unresolved.add(dep);
+                } else {
+                    resolved.add(resolvedId);
+                }
+            }
+        }
+
+        return new DependencyReportImpl(id, unresolved.toArray(new Dependency[unresolved.size()]),
+                resolved.toArray(new PackageId[resolved.size()])
+        );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public abstract PackageId resolve(Dependency dependency, boolean onlyInstalled) throws IOException;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Nonnull
+    @Override
+    public PackageId[] usage(PackageId id) throws IOException {
+        TreeSet<PackageId> usages = new TreeSet<PackageId>();
+        for (PackageId pid : packages()) {
+            try (RegisteredPackage pkg = open(pid)) {
+                if (pkg == null || !pkg.isInstalled()) {
+                    continue;
+                }
+                // noinspection resource
+                for (Dependency dep : pkg.getPackage().getDependencies()) {
+                    if (dep.matches(id)) {
+                        usages.add(pid);
+                        break;
+                    }
+                }
+            }
+        }
+        return usages.toArray(new PackageId[usages.size()]);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Nonnull
+    @Override
+    public ExecutionPlanBuilder createExecutionPlan() {
+        return new ExecutionPlanBuilderImpl(this);
+    }
+
+    /**
+     * Returns the relative path of this package. please note that since 2.3 this also
+     * includes the version, but never the extension (.zip).
+     *
+     * @param id the package id
+     * @return the relative path of this package
+     * @since 2.2
+     */
+    public String getRelativeInstallationPath(PackageId id) {
+        StringBuilder b = new StringBuilder("/");
+        if (id.getGroup().length() > 0) {
+            b.append(id.getGroup());
+            b.append("/");
+        }
+        b.append(id.getName());
+        String v = id.getVersion().toString();
+        if (v.length() > 0) {
+            b.append("-").append(v);
+        }
+        return b.toString();
+    }
+
+
+     /**
+     * Creates a random package id for packages that lack one.
+     * 
+     * @return a random package id.
+     */
+    protected static PackageId createRandomPid() {
+        return new PackageId("temporary", "pack_" + UUID.randomUUID().toString(), (String) null);
+    }
+
+
+}

Modified: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/ExecutionPlanBuilderImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/ExecutionPlanBuilderImpl.java?rev=1836554&r1=1836553&r2=1836554&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/ExecutionPlanBuilderImpl.java (original)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/ExecutionPlanBuilderImpl.java Tue Jul 24 14:32:12 2018
@@ -44,6 +44,7 @@ import org.apache.jackrabbit.vault.packa
 import org.apache.jackrabbit.vault.packaging.registry.DependencyReport;
 import org.apache.jackrabbit.vault.packaging.registry.ExecutionPlan;
 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.RegisteredPackage;
@@ -81,7 +82,7 @@ public class ExecutionPlanBuilderImpl im
 
     private final List<TaskBuilder> tasks = new LinkedList<TaskBuilder>();
 
-    private final JcrPackageRegistry registry;
+    private final PackageRegistry registry;
 
     private Session session;
 
@@ -89,7 +90,7 @@ public class ExecutionPlanBuilderImpl im
 
     private ExecutionPlanImpl plan;
 
-    ExecutionPlanBuilderImpl(JcrPackageRegistry registry) {
+    ExecutionPlanBuilderImpl(PackageRegistry registry) {
         this.registry = registry;
     }
 

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSInstallState.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSInstallState.java?rev=1836554&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSInstallState.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSInstallState.java Tue Jul 24 14:32:12 2018
@@ -0,0 +1,285 @@
+/*
+ * 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.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.vault.packaging.Dependency;
+import org.apache.jackrabbit.vault.packaging.PackageId;
+import org.apache.jackrabbit.vault.packaging.SubPackageHandling;
+import org.apache.jackrabbit.vault.util.RejectingEntityResolver;
+import org.apache.jackrabbit.vault.util.xml.serialize.OutputFormat;
+import org.apache.jackrabbit.vault.util.xml.serialize.XMLSerializer;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Internal (immutable) State object to cache and pass the relevant metadata around.
+ */
+public class FSInstallState {
+
+    private static final String TAG_REGISTRY_METADATA = "registryMetadata";
+
+    private static final String ATTR_PACKAGE_ID = "packageid";
+
+    private static final String ATTR_FILE_PATH = "filepath";
+
+    private static final String ATTR_PACKAGE_STATUS = "packagestatus";
+
+    private static final String ATTR_EXTERNAL = "external";
+
+    private static final String TAG_DEPENDENCY = "dependency";
+
+    private static final String TAG_SUBPACKAGE = "subpackage";
+
+    private static final String ATTR_INSTALLATION_TIME = "installtime";
+
+    private static final String ATTR_SUBPACKAGE_HANDLING_OPTION = "sphoption";
+
+    private final PackageId packageId;
+    private final FSPackageStatus status;
+    private Path filePath;
+    private boolean external;
+    private Set<Dependency> dependencies = Collections.emptySet();
+    private Map<PackageId, SubPackageHandling.Option> subPackages = Collections.emptyMap();
+    private Long installTime;
+
+    public FSInstallState(@Nonnull PackageId pid, @Nonnull FSPackageStatus status) {
+        this.packageId = pid;
+        this.status = status;
+    }
+
+    public FSInstallState withFilePath(Path filePath) {
+        this.filePath = filePath;
+        return this;
+    }
+
+    public FSInstallState withExternal(boolean external) {
+        this.external = external;
+        return this;
+    }
+
+    public FSInstallState withDependencies(Set<Dependency> dependencies) {
+        this.dependencies = dependencies == null
+                ? Collections.<Dependency>emptySet()
+                : Collections.unmodifiableSet(dependencies);
+        return this;
+    }
+
+    public FSInstallState withSubPackages(Map<PackageId, SubPackageHandling.Option> subPackages) {
+        this.subPackages = subPackages == null
+                ? Collections.<PackageId, SubPackageHandling.Option>emptyMap()
+                : Collections.unmodifiableMap(subPackages);
+        return this;
+    }
+
+    public FSInstallState withInstallTime(Long installTime) {
+        this.installTime = installTime;
+        return this;
+    }
+
+    /**
+     * Parses {@code InstallState} from metafile.
+     *
+     * @param metaFile The meta file.
+     * @return Install state or null if file is not metafile format
+     *
+     * @throws IOException in case root tag is correct but structure not parsable as expected
+     */
+    @Nullable
+    public static FSInstallState fromFile(File metaFile) throws IOException {
+        if (!metaFile.exists()) {
+            return null;
+        }
+        try (InputStream in = FileUtils.openInputStream(metaFile)) {
+            return fromStream(in, metaFile.getPath());
+        }
+    }
+
+    /**
+     * Parses {@code InstallState} from metafile.
+     *
+     * @param in The input stream
+     * @param systemId the id of the stream
+     * @return Install state or null if file is not metafile format
+     *
+     * @throws IOException in case root tag is correct but structure not parsable as expected
+     */
+    @Nullable
+    public static FSInstallState fromStream(InputStream in, String systemId) throws IOException {
+        try {
+            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+            factory.setNamespaceAware(true);
+            DocumentBuilder builder = factory.newDocumentBuilder();
+            builder.setEntityResolver(new RejectingEntityResolver());
+            Document document = builder.parse(in, systemId);
+            Element doc = document.getDocumentElement();
+            if (!TAG_REGISTRY_METADATA.equals(doc.getNodeName())) {
+                return null;
+            }
+            String packageId = doc.getAttribute(ATTR_PACKAGE_ID);
+            Path filePath = Paths.get(doc.getAttribute(ATTR_FILE_PATH));
+            Long installTime = null;
+            if (doc.hasAttribute(ATTR_INSTALLATION_TIME)) {
+                installTime = Long.valueOf(doc.getAttribute(ATTR_INSTALLATION_TIME));
+            }
+            boolean external = Boolean.parseBoolean(doc.getAttribute(ATTR_EXTERNAL));
+            FSPackageStatus status = FSPackageStatus.valueOf(doc.getAttribute(ATTR_PACKAGE_STATUS).toUpperCase());
+            NodeList nl = doc.getChildNodes();
+            Set<Dependency> dependencies = new HashSet<>();
+            Map<PackageId, SubPackageHandling.Option> subPackages = new HashMap<>();
+            for (int i = 0; i < nl.getLength(); i++) {
+                Node child = nl.item(i);
+                if (child.getNodeType() == Node.ELEMENT_NODE) {
+                    String childName = child.getNodeName();
+                    if (TAG_DEPENDENCY.equals(childName)) {
+                        dependencies.add(readDependency((Element) child));
+                    } else if (TAG_SUBPACKAGE.equals(childName)) {
+                        subPackages.put(readPackageId((Element) child), readSubPackgeHandlingOption((Element) child));
+                    } else {
+                        throw new IOException("<" + TAG_DEPENDENCY + "> or <" + TAG_SUBPACKAGE + "> expected.");
+                    }
+                }
+            }
+            return new FSInstallState(PackageId.fromString(packageId), status)
+                    .withFilePath(filePath)
+                    .withExternal(external)
+                    .withDependencies(dependencies)
+                    .withSubPackages(subPackages)
+                    .withInstallTime(installTime);
+        } catch (ParserConfigurationException e) {
+            throw new IOException("Unable to create configuration XML parser", e);
+        } catch (SAXException e) {
+            throw new IOException("Configuration file syntax error.", e);
+        }
+    }
+
+    private static Dependency readDependency(Element child) {
+        return Dependency.fromString(child.getAttribute(ATTR_PACKAGE_ID));
+    }
+
+    private static SubPackageHandling.Option readSubPackgeHandlingOption(Element child) {
+        return SubPackageHandling.Option.valueOf(child.getAttribute(ATTR_SUBPACKAGE_HANDLING_OPTION));
+    }
+    
+    private static PackageId readPackageId(Element child) {
+        return PackageId.fromString(child.getAttribute(ATTR_PACKAGE_ID));
+    }
+
+
+    /**
+     * Persists the installState to a metadatafile
+     * @param file The files to save the state to
+     * @throws IOException if an error occurs.
+     */
+    public void save(File file) throws IOException {
+        try (OutputStream out = FileUtils.openOutputStream(file)) {
+            save(out);
+        }
+    }
+
+    /**
+     * Persists the installState to a metadatafile
+     * @param out Outputsteam to write to.
+     * @throws IOException if an error occurs.
+     */
+    public void save(OutputStream out) throws IOException {
+        try {
+            XMLSerializer ser = new XMLSerializer(out, new OutputFormat("xml", "UTF-8", true));
+            ser.startDocument();
+            AttributesImpl attrs = new AttributesImpl();
+            attrs.addAttribute(null, null, ATTR_PACKAGE_ID, "CDATA", packageId.toString());
+            if (installTime != null) {
+                attrs.addAttribute(null, null, ATTR_INSTALLATION_TIME, "CDATA", Long.toString(installTime));
+            }
+            attrs.addAttribute(null, null, ATTR_FILE_PATH, "CDATA", filePath.toString());
+            attrs.addAttribute(null, null, ATTR_EXTERNAL, "CDATA", Boolean.toString(external));
+            attrs.addAttribute(null, null, ATTR_PACKAGE_STATUS, "CDATA", status.name().toLowerCase());
+            ser.startElement(null, null, TAG_REGISTRY_METADATA, attrs);
+            if (dependencies != null) {
+                for (Dependency dependency : dependencies) {
+                    attrs = new AttributesImpl();
+                    attrs.addAttribute(null, null, ATTR_PACKAGE_ID, "CDATA", dependency.toString());
+                    ser.startElement(null, null, TAG_DEPENDENCY, attrs);
+                    ser.endElement(TAG_DEPENDENCY);
+                }
+            }
+            if (subPackages != null) {
+                for (PackageId subPackId : subPackages.keySet()) {
+                    attrs = new AttributesImpl();
+                    attrs.addAttribute(null, null, ATTR_PACKAGE_ID, "CDATA", subPackId.toString());
+                    attrs.addAttribute(null, null, ATTR_SUBPACKAGE_HANDLING_OPTION, "CDATA", subPackages.get(subPackId).toString());
+                    ser.startElement(null, null, TAG_SUBPACKAGE, attrs);
+                    ser.endElement(TAG_SUBPACKAGE);
+                }
+            }
+            ser.endElement(TAG_REGISTRY_METADATA);
+            ser.endDocument();
+        } catch (SAXException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    public Long getInstallationTime() {
+        return installTime;
+    }
+
+    public Map<PackageId, SubPackageHandling.Option> getSubPackages() {
+        return subPackages;
+    }
+
+    public PackageId getPackageId() {
+        return packageId;
+    }
+
+    public Path getFilePath() {
+        return filePath;
+    }
+
+    public boolean isExternal() {
+        return external;
+    }
+
+    public FSPackageStatus getStatus() {
+        return status;
+    }
+
+    public Set<Dependency> getDependencies() {
+        return dependencies;
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSPackageRegistry.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSPackageRegistry.java?rev=1836554&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSPackageRegistry.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSPackageRegistry.java Tue Jul 24 14:32:12 2018
@@ -0,0 +1,712 @@
+/*
+ * 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.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.vault.fs.api.PathFilterSet;
+import org.apache.jackrabbit.vault.fs.config.MetaInf;
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
+import org.apache.jackrabbit.vault.fs.io.MemoryArchive;
+import org.apache.jackrabbit.vault.packaging.Dependency;
+import org.apache.jackrabbit.vault.packaging.NoSuchPackageException;
+import org.apache.jackrabbit.vault.packaging.PackageException;
+import org.apache.jackrabbit.vault.packaging.PackageExistsException;
+import org.apache.jackrabbit.vault.packaging.PackageId;
+import org.apache.jackrabbit.vault.packaging.SubPackageHandling;
+import org.apache.jackrabbit.vault.packaging.VaultPackage;
+import org.apache.jackrabbit.vault.packaging.events.PackageEvent;
+import org.apache.jackrabbit.vault.packaging.events.PackageEvent.Type;
+import org.apache.jackrabbit.vault.packaging.events.impl.PackageEventDispatcher;
+import org.apache.jackrabbit.vault.packaging.impl.PackagePropertiesImpl;
+import org.apache.jackrabbit.vault.packaging.impl.ZipVaultPackage;
+import org.apache.jackrabbit.vault.packaging.registry.DependencyReport;
+import org.apache.jackrabbit.vault.packaging.registry.PackageRegistry;
+import org.apache.jackrabbit.vault.packaging.registry.RegisteredPackage;
+import org.apache.jackrabbit.vault.util.InputStreamPump;
+import org.apache.jackrabbit.vault.util.PlatformNameFormat;
+import org.apache.jackrabbit.vault.util.Text;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * FileSystem based registry not depending on a JCR Session. All metadata is stored in Filesystem and can be prepared and used without a running JCR repository
+ * Only methods to install or uninstall packages require an active {@code Session} object of running jcr instance to perform the actual installation tasks
+ */
+@Component(
+        service = PackageRegistry.class,
+        configurationPolicy = ConfigurationPolicy.REQUIRE,
+        property = {"service.vendor=The Apache Software Foundation"}
+)
+@Designate(ocd = FSPackageRegistry.Config.class)
+public class FSPackageRegistry extends AbstractPackageRegistry {
+
+    private static final String REPOSITORY_HOME = "repository.home";
+
+    /**
+     * default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(FSPackageRegistry.class);
+
+    /**
+     * Suffixes for metadata files
+     */
+    private final String[] META_SUFFIXES = {"xml"};
+
+    private Map<PackageId, FSInstallState> stateCache = new ConcurrentHashMap<>();
+
+    /**
+     * Contains a map of all filesystem paths to package IDs
+     */
+    private Map<Path, PackageId> pathIdMapping = new ConcurrentHashMap<>();
+
+
+    private boolean packagesInitializied = false;
+
+    @Reference
+    private PackageEventDispatcher dispatcher;
+
+    private File homeDir;
+
+    private File getHomeDir() {
+        return homeDir;
+    }
+
+    /**
+     * Creates a new FSPackageRegistry based on the given home directory.
+     *
+     * @param homeDir the directory in which packages and their metadata is stored
+     * @throws IOException If an I/O error occurs.
+     */
+    public FSPackageRegistry(@Nonnull File homeDir) throws IOException {
+        this.homeDir = homeDir;
+        loadPackageCache();
+    }
+
+    /**
+     * Deafult constructor for OSGi initialization (homeDir defined via activator)
+     */
+    public FSPackageRegistry() {
+    }
+
+
+    @ObjectClassDefinition(
+            name = "Apache Jackrabbit FS Package Registry Service"
+    )
+    @interface Config {
+
+        @AttributeDefinition
+        String homePath() default "packageregistry";
+    }
+
+    @Activate
+    private void activate(BundleContext context, Config config) throws IOException {
+        String repoHome = context.getProperty(REPOSITORY_HOME);
+        if (repoHome == null) {
+            this.homeDir = context.getDataFile(config.homePath());
+        } else {
+            this.homeDir = new File(repoHome + "/" + config.homePath());
+            if (!homeDir.exists()) {
+                homeDir.mkdirs();
+            }
+        }
+        loadPackageCache();
+        log.info("Jackrabbit Filevault FS Package Registry initialized with home location {}", this.homeDir.getPath());
+    }
+
+    /**
+     * Sets the event dispatcher
+     *
+     * @param dispatcher the dispatcher.
+     */
+    public void setDispatcher(@Nullable PackageEventDispatcher dispatcher) {
+        this.dispatcher = dispatcher;
+    }
+
+    /**
+     * Dispatches a package event using the configured dispatcher.
+     *
+     * @param type event type
+     * @param id package id
+     * @param related related packages
+     */
+    public void dispatch(@Nonnull PackageEvent.Type type, @Nonnull PackageId id, @Nullable PackageId[] related) {
+        if (dispatcher == null) {
+            return;
+        }
+        dispatcher.dispatch(type, id, related);
+    }
+
+    @Nullable
+    @Override
+    public RegisteredPackage open(@Nonnull PackageId id) throws IOException {
+        try {
+            File pkg = getPackageFile(id);
+            return pkg != null && pkg.exists() ? new FSRegisteredPackage(this, open(pkg)) : null;
+        } catch (RepositoryException e) {
+            throw new IOException(e);
+        }
+    }
+
+    @Override
+    public boolean contains(@Nonnull PackageId id) throws IOException {
+        return stateCache.containsKey(id);
+    }
+
+    @Nullable
+    private File getPackageFile(@Nonnull PackageId id) {
+        try {
+            FSInstallState state = getInstallState(id);
+            if (FSPackageStatus.NOTREGISTERED == state.getStatus()) {
+                return buildPackageFile(id);
+            } else {
+                return state.getFilePath().toFile();
+            }
+        } catch (IOException e) {
+            log.error("Couldn't get install state of packageId {}", id, e);
+        }
+        return null;
+    }
+
+    private File buildPackageFile(@Nonnull PackageId id) {
+        String path = getInstallationPath(id);
+        return new File(getHomeDir(), path + ".zip");
+    }
+
+    /**
+     * Returns the meta data file of the package with the given Id.
+     *
+     * @param id The package Id.
+     * @return the meta data file.
+     */
+    @Nonnull
+    private File getPackageMetaDataFile(@Nonnull PackageId id) {
+        final String path = getInstallationPath(id);
+        return new File(getHomeDir(), path + ".xml");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public VaultPackage open(File pkg) throws RepositoryException {
+        try {
+            return new ZipVaultPackage(pkg, false, true);
+        } catch (IOException e) {
+            log.error("Cloud not open file {} as ZipVaultPackage.", pkg.getPath(), e);
+            return null;
+        }
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Nonnull
+    @Override
+    public DependencyReport analyzeDependencies(@Nonnull PackageId id, boolean onlyInstalled) throws IOException, NoSuchPackageException {
+        List<Dependency> unresolved = new LinkedList<>();
+        List<PackageId> resolved = new LinkedList<>();
+        FSInstallState state = getInstallState(id);
+        if (FSPackageStatus.NOTREGISTERED == state.getStatus()) {
+            throw new NoSuchPackageException().setId(id);
+        }
+
+        // Make sure that also dependencies of contained packages are considered as packages will be installed in a joined sequence.
+        Set<Dependency> allDependencies = new HashSet<>();
+        allDependencies.addAll(state.getDependencies());
+        for (PackageId subId : state.getSubPackages().keySet()) {
+            FSInstallState subState = getInstallState(subId);
+            allDependencies.addAll(subState.getDependencies());
+        }
+
+        for (Dependency dep : allDependencies) {
+            PackageId resolvedId = resolve(dep, onlyInstalled);
+            if (resolvedId == null) {
+                unresolved.add(dep);
+            } else {
+                resolved.add(resolvedId);
+            }
+        }
+
+        return new DependencyReportImpl(id, unresolved.toArray(new Dependency[unresolved.size()]),
+                resolved.toArray(new PackageId[resolved.size()])
+        );
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public PackageId resolve(Dependency dependency, boolean onlyInstalled) throws IOException {
+        PackageId bestId = null;
+        for (PackageId id : packages()) {
+            if (!onlyInstalled || isInstalled(id)) {
+                if (dependency.matches(id)) {
+                    if (bestId == null || id.getVersion().compareTo(bestId.getVersion()) > 0) {
+                        bestId = id;
+                    }
+                }
+            }
+        }
+        return bestId;
+    }
+
+    /**
+     * Returns {@code true} when state {@link FSPackageStatus#EXTRACTED} is recorded for given {@code PackageId}
+     *
+     * @param id PackageId of the package to test.
+     * @return {@code true} if package is in state {@link FSPackageStatus#EXTRACTED}
+     *
+     * @throws IOException If an I/O error occurs.
+     */
+    boolean isInstalled(PackageId id) throws IOException {
+        FSPackageStatus status = getInstallState(id).getStatus();
+        return FSPackageStatus.EXTRACTED == status;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Nonnull
+    @Override
+    public PackageId register(@Nonnull InputStream in, boolean replace) throws IOException, PackageExistsException {
+      return register(in, replace, null);
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Nonnull
+    private PackageId register(@Nonnull InputStream in, boolean replace, Dependency autoDependency) throws IOException, PackageExistsException {
+        ZipVaultPackage pkg = upload(in, replace);
+
+        Map<PackageId, SubPackageHandling.Option> subpackages = registerSubPackages(pkg, replace);
+        File pkgFile = buildPackageFile(pkg.getId());
+        HashSet<Dependency> dependencies = new HashSet<>();
+        dependencies.addAll(Arrays.asList(pkg.getDependencies()));
+        if (autoDependency != null) {
+            dependencies.add(autoDependency);
+        }
+        setInstallState(pkg.getId(), FSPackageStatus.REGISTERED, pkgFile.toPath(), false, dependencies, subpackages,
+                null);
+        return pkg.getId();
+    }
+
+    /**
+     * Registers subpackages in registry
+     *
+     * @param pkg The package to regist
+     * @param replace {@code true} to replace
+     * @return {@code Map} of {@code PackageId}s along with the corresponding {@code SubPackageHandling.Option} registered from a given {@code VaultPackage}
+     *
+     * @throws IOException
+     * @throws PackageExistsException
+     */
+    private Map<PackageId, SubPackageHandling.Option> registerSubPackages(VaultPackage pkg, boolean replace)
+            throws IOException, PackageExistsException {
+        Map<PackageId, SubPackageHandling.Option> subpackages = new HashMap<>();
+
+        Archive.Entry packagesRoot = pkg.getArchive().getEntry(ARCHIVE_PACKAGE_ROOT_PATH);
+        if (packagesRoot != null) { 
+            // As for JcrPackageImpl subpackages need to get an implicit autoDependency to the parent in case they have own content
+            boolean hasOwnContent = false;
+            for (PathFilterSet root : pkg.getArchive().getMetaInf().getFilter().getFilterSets()) {
+                // todo: find better way to detect subpackages
+                if (!Text.isDescendantOrEqual(DEFAULT_PACKAGE_ROOT_PATH, root.getRoot())) {
+                    log.debug(
+                            "Package {}: contains content outside /etc/packages. Sub packages will have a dependency to it",
+                            pkg.getId());
+                    hasOwnContent = true;
+                }
+            }
+            Dependency autoDependency = hasOwnContent ? new Dependency(pkg.getId()) : null;
+            registerSubPackages(pkg, packagesRoot, DEFAULT_PACKAGE_ROOT_PATH, replace, subpackages, autoDependency);
+            dispatch(Type.EXTRACT_SUB_PACKAGES, pkg.getId(), subpackages.keySet().toArray(new PackageId[subpackages.size()]));
+        }
+        return subpackages;
+    }
+
+    /**
+     * Parses given {@link Archive.Entry} for .jar & .zip binaries and tries to register given subpackage.
+     *
+     * @param vltPkg
+     * @param directory
+     * @param parentPath
+     * @param replace
+     * @param subpackages
+     * @throws IOException
+     * @throws PackageExistsException
+     */
+    private void registerSubPackages(VaultPackage vltPkg, Archive.Entry directory, String parentPath, boolean replace, Map<PackageId, SubPackageHandling.Option> subpackages, Dependency autoDependency)
+            throws IOException, PackageExistsException {
+        Collection<? extends Archive.Entry> files = directory.getChildren();
+
+        for (Archive.Entry file : files) {
+            String fileName = file.getName();
+            String repoName = PlatformNameFormat.getRepositoryName(fileName);
+            String repoPath = parentPath + "/" + repoName;
+            if (file.isDirectory()) {
+                registerSubPackages(vltPkg, file, repoPath, replace, subpackages, autoDependency);
+            } else {
+                if (repoPath.startsWith(DEFAULT_PACKAGE_ROOT_PATH_PREFIX) && (repoPath.endsWith(".jar") || repoPath.endsWith(".zip"))) {
+                    try (InputStream in = vltPkg.getArchive().openInputStream(file)) {
+                        if (in == null) {
+                            throw new IOException("Unable to open archive input stream of " + file);
+                        }
+                        PackageId id = register(in, replace);
+                        SubPackageHandling.Option option = vltPkg.getSubPackageHandling().getOption(id);
+                        subpackages.put(id, option);
+                    } catch (PackageExistsException e) {
+                        log.info("Subpackage already registered, skipping subpackage extraction.");
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ZipVaultPackage upload(InputStream in, boolean replace)
+            throws IOException, PackageExistsException {
+
+        MemoryArchive archive = new MemoryArchive(false);
+        File tempFile = File.createTempFile("upload", ".zip");
+
+        try (InputStreamPump pump = new InputStreamPump(in, archive)) {
+            // this will cause the input stream to be consumed and the memory
+            // archive being initialized.
+            try {
+
+                FileUtils.copyInputStreamToFile(pump, tempFile);
+            } catch (Exception e) {
+                String msg = "Stream could be read successfully.";
+                log.error(msg);
+                throw new IOException(msg, e);
+            }
+
+            if (archive.getJcrRoot() == null) {
+                String msg = "Stream is not a content package. Missing 'jcr_root'.";
+                log.error(msg);
+                throw new IOException(msg);
+            }
+
+            final MetaInf inf = archive.getMetaInf();
+            PackagePropertiesImpl props = new PackagePropertiesImpl() {
+                @Override
+                protected Properties getPropertiesMap() {
+                    return inf.getProperties();
+                }
+            };
+            PackageId pid = props.getId();
+
+            // invalidate pid if path is unknown
+            if (pid == null) {
+                throw new IllegalArgumentException("Unable to create package. No package pid set.");
+            }
+            if (!pid.isValid()) {
+                throw new IllegalArgumentException("Unable to create package. Illegal package name.");
+            }
+
+            File oldPkgFile = getPackageFile(pid);
+            FSInstallState state = getInstallState(pid);
+
+            if (oldPkgFile != null && oldPkgFile.exists()) {
+                if (replace && !state.isExternal()) {
+                    oldPkgFile.delete();
+                } else {
+                    throw new PackageExistsException("Package already exists: " + pid).setId(pid);
+                }
+            }
+
+            ZipVaultPackage pkg = new ZipVaultPackage(archive, true);
+            registerSubPackages(pkg, replace);
+            File pkgFile = buildPackageFile(pid);
+            FileUtils.moveFile(tempFile, pkgFile);
+            dispatch(Type.UPLOAD, pid, null);
+            return pkg;
+        }
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Nonnull
+    @Override
+    public PackageId register(@Nonnull File file, boolean replace) throws IOException, PackageExistsException {
+        ZipVaultPackage pack = new ZipVaultPackage(file, false, true);
+        try {
+            File pkgFile = buildPackageFile(pack.getId());
+            if (pkgFile.exists()) {
+                if (replace) {
+                    pkgFile.delete();
+                } else {
+                    throw new PackageExistsException("Package already exists: " + pack.getId()).setId(pack.getId());
+                }
+            }
+            Map<PackageId, SubPackageHandling.Option> subpackages = registerSubPackages(pack, replace);
+            FileUtils.copyFile(file, pkgFile);
+            Collection<Dependency> dependencies = Arrays.asList(pack.getDependencies());
+            setInstallState(pack.getId(), FSPackageStatus.REGISTERED, pkgFile.toPath(), false, new HashSet<>(dependencies), subpackages, null);
+            return pack.getId();
+        } finally {
+            if (!pack.isClosed()) {
+                pack.close();
+            }
+        }
+    }
+
+    @Nonnull
+    @Override
+    public PackageId registerExternal(@Nonnull File file, boolean replace) throws IOException, PackageExistsException {
+        if (!replace && pathIdMapping.containsKey(file.getPath())) {
+            throw new PackageExistsException("Package already exists: " + pathIdMapping.get(file.getPath()));
+        }
+        ZipVaultPackage pack = new ZipVaultPackage(file, false, true);
+        try {
+
+            FSInstallState state = getInstallState(pack.getId());
+            if (!(FSPackageStatus.NOTREGISTERED == state.getStatus())) {
+                if (replace) {
+                    try {
+                        remove(pack.getId());
+                    } catch (NoSuchPackageException e) {
+                        log.error("Status isn't NOTREGISTERD but no metafile exists to remove", e);
+                    }
+                } else {
+                    throw new PackageExistsException("Package already exists: " + pack.getId()).setId(pack.getId());
+                }
+            }
+            Map<PackageId, SubPackageHandling.Option> subpackages = registerSubPackages(pack, replace);
+            Collection<Dependency> dependencies = Arrays.asList(pack.getDependencies());
+            setInstallState(pack.getId(), FSPackageStatus.REGISTERED, file.toPath(), true, new HashSet<>(dependencies), subpackages, null);
+            return pack.getId();
+        } finally {
+            if (!pack.isClosed()) {
+                pack.close();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void remove(@Nonnull PackageId id) throws IOException, NoSuchPackageException {
+        FSInstallState state = getInstallState(id);
+        File metaData = getPackageMetaDataFile(id);
+
+        if (!metaData.exists()) {
+            throw new NoSuchPackageException().setId(id);
+        }
+        metaData.delete();
+
+        if (!state.isExternal()) {
+            getPackageFile(id).delete();
+        }
+        updateInstallState(id, FSPackageStatus.NOTREGISTERED);
+        dispatch(PackageEvent.Type.REMOVE, id, null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Nonnull
+    @Override
+    public Set<PackageId> packages() throws IOException {
+        return packagesInitializied ? stateCache.keySet() : loadPackageCache();
+    }
+
+    /**
+     * Loads all state from files persisted in configured homeDir, adds to cache and returns all cached {@code PackageId}s.
+     *
+     * @return {@code Set} of all cached {@code PackageId}s
+     *
+     * @throws IOException If an I/O error occurs
+     */
+    private Set<PackageId> loadPackageCache() throws IOException {
+        Map<PackageId, FSInstallState> cacheEntries = new HashMap<>();
+        Map<Path, PackageId> idMapping = new HashMap<>();
+
+
+        Collection<File> files = FileUtils.listFiles(getHomeDir(), META_SUFFIXES, true);
+        for (File file : files) {
+            FSInstallState state = FSInstallState.fromFile(file);
+            if (state != null) {
+                PackageId id = state.getPackageId();
+                if (id != null) {
+                    cacheEntries.put(id, state);
+                    idMapping.put(state.getFilePath(), id);
+                    
+                }
+            }
+        }
+        stateCache.putAll(cacheEntries);
+        pathIdMapping.putAll(idMapping);
+        packagesInitializied = true;
+        return cacheEntries.keySet();
+    }
+
+    /**
+     * Returns the path of this package.this also includes the version, but
+     * never the extension (.zip).
+     *
+     * @param id the package id
+     * @return the path of this package
+     */
+    public String getInstallationPath(PackageId id) {
+        return getRelativeInstallationPath(id);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void installPackage(@Nonnull Session session, @Nonnull RegisteredPackage pkg, @Nonnull ImportOptions opts,
+                               boolean extract) throws IOException, PackageException {
+
+        // For now FS based persistence only supports extraction but no reversible installation
+        if (!extract) {
+            String msg = "Only extraction supported by FS based registry";
+            log.error(msg);
+            throw new PackageException(msg);
+        }
+        try (VaultPackage vltPkg = pkg.getPackage()) {
+            vltPkg.extract(session, opts);
+            dispatch(PackageEvent.Type.EXTRACT, pkg.getId(), null);
+            updateInstallState(vltPkg.getId(), FSPackageStatus.EXTRACTED);
+
+        } catch (RepositoryException e) {
+            throw new IOException(e);
+        }
+    }
+
+    /**
+     * Uninstallation not supported for FS based PackageRegistry
+     */
+    @Override
+    public void uninstallPackage(@Nonnull Session session, @Nonnull RegisteredPackage pkg, @Nonnull ImportOptions opts) throws IOException, PackageException {
+        String msg = "Uninstallation not supported by FS based registry";
+        log.error(msg);
+        throw new PackageException(msg);
+    }
+
+    /**
+     * Shortcut to just change the status of a package - implicitly sets the installtime when switching to EXTRACTED
+     *
+     * @param pid PackageId of the package to update
+     * @param targetStatus Status to update
+     * @throws IOException If an I/O error occurs.
+     */
+    private void updateInstallState(PackageId pid, FSPackageStatus targetStatus) throws IOException {
+        FSInstallState state = getInstallState(pid);
+        Long installTime = state.getInstallationTime();
+        if (FSPackageStatus.EXTRACTED == targetStatus) {
+            installTime = Calendar.getInstance().getTimeInMillis();
+        }
+        setInstallState(pid, targetStatus, state.getFilePath(), state.isExternal(), state.getDependencies(), state.getSubPackages(), installTime);
+    }
+
+    /**
+     * Persists the installState to a metadatafile and adds current state to cache
+     *
+     * @param pid
+     * @param targetStatus
+     * @param filePath
+     * @param external
+     * @param dependencies
+     * @param subPackages
+     * @param installTimeStamp
+     * @throws IOException
+     */
+    private void setInstallState(@Nonnull PackageId pid, @Nonnull FSPackageStatus targetStatus, @Nonnull Path filePath, @Nonnull boolean external, @Nullable Set<Dependency> dependencies, @Nullable Map<PackageId, SubPackageHandling.Option> subPackages, @Nullable Long installTimeStamp) throws IOException {
+        File metaData = getPackageMetaDataFile(pid);
+
+        if (targetStatus == FSPackageStatus.NOTREGISTERED) {
+            pathIdMapping.remove(stateCache.get(pid).getFilePath());
+            metaData.delete();
+            stateCache.remove(pid);
+        } else {
+            FSInstallState state = new FSInstallState(pid, targetStatus)
+                    .withFilePath(filePath)
+                    .withDependencies(dependencies)
+                    .withSubPackages(subPackages)
+                    .withInstallTime(installTimeStamp)
+                    .withExternal(external);
+            state.save(metaData);
+            stateCache.put(pid, state);
+            pathIdMapping.put(state.getFilePath(), pid);
+        }
+    }
+
+    /**
+     * Retrieves {@code InstallState} from cache, falls back to reading from metafile and returns state for {@code FSPackageStatus.NOTREGISTERED} in case not found.
+     *
+     * @param pid the PackageId of the package to retrieve the install state from.
+     * @return {@code InstallState} found for given {@code PackageId} or a fresh one with status {@code FSPackageStatus.NOTREGISTERED}
+     *
+     * @throws IOException if an I/O error occurs.
+     */
+    @Nonnull
+    public FSInstallState getInstallState(PackageId pid) throws IOException {
+        if (stateCache.containsKey(pid)) {
+            return stateCache.get(pid);
+        } else {
+            File metaFile = getPackageMetaDataFile(pid);
+            FSInstallState state = FSInstallState.fromFile(metaFile);
+            if (state != null) {
+                //theoretical file - should only be feasible when manipulating on filesystem, writing metafile automatically updates cache
+                stateCache.put(pid, state);
+                pathIdMapping.put(state.getFilePath(), pid);
+            }
+            return state != null ? state : new FSInstallState(pid, FSPackageStatus.NOTREGISTERED);
+        }
+    }
+
+
+}

Copied: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSPackageStatus.java (from r1833974, jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/package-info.java)
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSPackageStatus.java?p2=jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSPackageStatus.java&p1=jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/package-info.java&r1=1833974&r2=1836554&rev=1836554&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/package-info.java (original)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSPackageStatus.java Tue Jul 24 14:32:12 2018
@@ -14,5 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@org.osgi.annotation.versioning.Version("1.0.0")
-package org.apache.jackrabbit.vault.packaging.registry;
+package org.apache.jackrabbit.vault.packaging.registry.impl;
+
+/**
+ * Internal Status a package may have in {@code FSPackageRegistry}
+ */
+public enum FSPackageStatus {
+    
+    REGISTERED,
+    EXTRACTED,
+    NOTREGISTERED
+
+}

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSRegisteredPackage.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSRegisteredPackage.java?rev=1836554&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSRegisteredPackage.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/FSRegisteredPackage.java Tue Jul 24 14:32:12 2018
@@ -0,0 +1,106 @@
+/*
+ * 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.IOException;
+import java.util.Calendar;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.vault.packaging.PackageId;
+import org.apache.jackrabbit.vault.packaging.VaultPackage;
+import org.apache.jackrabbit.vault.packaging.registry.RegisteredPackage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@code JcrRegisteredPackage}...
+ */
+public class FSRegisteredPackage implements RegisteredPackage {
+
+    /**
+     * default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(FSPackageRegistry.class);
+
+    private VaultPackage vltPkg;
+    private FSPackageRegistry registry;
+
+    public FSRegisteredPackage(FSPackageRegistry registry, VaultPackage vltPkg) throws IOException, RepositoryException {
+        this.vltPkg = vltPkg;
+        this.registry = registry;
+    }
+
+    @Nonnull
+    @Override
+    public PackageId getId() {
+        return vltPkg.getId();
+    }
+
+    @Nonnull
+    @Override
+    public VaultPackage getPackage() throws IOException {
+        return vltPkg;
+    }
+
+    @Override
+    public boolean isInstalled() {
+            try {
+                return registry.isInstalled(getId());
+            } catch (IOException e) {
+                log.error("Packagestate couldn't be read for package {}", getId().toString(), e);
+                return false;
+            }
+    }
+
+    @Override
+    public long getSize() {
+        return vltPkg.getSize();
+    }
+
+    @CheckForNull
+    @Override
+    public Calendar getInstallationTime() {
+        Calendar cal = Calendar.getInstance();
+        Long installTime;
+        try {
+            installTime = registry.getInstallState(getId()).getInstallationTime();
+            if (installTime == null) {
+                cal = null;
+            } else{
+                cal.setTimeInMillis(installTime);
+            }
+        } catch (IOException e) {
+            log.error("Could not read package state for package {}.", getId(), e);
+            cal = null;
+        }
+        return cal;
+    }
+
+    @Override
+    public void close() {
+        vltPkg.close();
+        vltPkg = null;
+    }
+
+    @Override
+    public int compareTo(RegisteredPackage o) {
+        return getId().compareTo(o.getId());
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/InternalPackageRegistry.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/InternalPackageRegistry.java?rev=1836554&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/InternalPackageRegistry.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/InternalPackageRegistry.java Tue Jul 24 14:32:12 2018
@@ -0,0 +1,37 @@
+/*
+ * 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.IOException;
+
+import javax.annotation.Nonnull;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
+import org.apache.jackrabbit.vault.packaging.PackageException;
+import org.apache.jackrabbit.vault.packaging.registry.PackageRegistry;
+import org.apache.jackrabbit.vault.packaging.registry.RegisteredPackage;
+
+public interface InternalPackageRegistry extends PackageRegistry {
+
+    void installPackage(@Nonnull Session session, @Nonnull RegisteredPackage pkg, @Nonnull ImportOptions opts,
+            boolean extract) throws IOException, PackageException;
+
+    void uninstallPackage(@Nonnull Session session, @Nonnull RegisteredPackage pkg, @Nonnull ImportOptions opts)
+            throws IOException, PackageException;
+
+}

Modified: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/JcrPackageRegistry.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/JcrPackageRegistry.java?rev=1836554&r1=1836553&r2=1836554&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/JcrPackageRegistry.java (original)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/JcrPackageRegistry.java Tue Jul 24 14:32:12 2018
@@ -23,8 +23,6 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.Calendar;
-import java.util.Collections;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Properties;
 import java.util.Set;
@@ -44,6 +42,7 @@ import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 import org.apache.jackrabbit.commons.JcrUtils;
 import org.apache.jackrabbit.vault.fs.config.MetaInf;
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
 import org.apache.jackrabbit.vault.fs.io.MemoryArchive;
 import org.apache.jackrabbit.vault.fs.spi.CNDReader;
 import org.apache.jackrabbit.vault.fs.spi.NodeTypeInstaller;
@@ -77,7 +76,7 @@ import org.slf4j.LoggerFactory;
 /**
  * {@code JcrPackagePersistence}...
  */
-public class JcrPackageRegistry implements PackageRegistry {
+public class JcrPackageRegistry extends AbstractPackageRegistry {
 
     /**
      * default logger
@@ -90,16 +89,6 @@ public class JcrPackageRegistry implemen
     private static final String DEFAULT_NODETYPES = "nodetypes.cnd";
 
     /**
-     * default root path for packages
-     */
-    public static final String DEFAULT_PACKAGE_ROOT_PATH = "/etc/packages";
-
-    /**
-     * default root path prefix for packages
-     */
-    public static final String DEFAULT_PACKAGE_ROOT_PATH_PREFIX = DEFAULT_PACKAGE_ROOT_PATH + "/";
-
-    /**
      * suggested folder types
      */
     private final static String[] FOLDER_TYPES = {"sling:Folder", "nt:folder", "nt:unstructured", null};
@@ -337,55 +326,6 @@ public class JcrPackageRegistry implemen
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    @Nonnull
-    @Override
-    public PackageId[] usage(PackageId id) throws IOException {
-        TreeSet<PackageId> usages = new TreeSet<PackageId>();
-        for (PackageId pid: packages()) {
-            try (RegisteredPackage pkg = open(pid)) {
-                if (pkg == null || !pkg.isInstalled()) {
-                    continue;
-                }
-                //noinspection resource
-                for (Dependency dep: pkg.getPackage().getDependencies()) {
-                    if (dep.matches(id)) {
-                        usages.add(pid);
-                        break;
-                    }
-                }
-            }
-        }
-        return usages.toArray(new PackageId[usages.size()]);
-    }
-
-    @Nonnull
-    @Override
-    public DependencyReport analyzeDependencies(@Nonnull PackageId id, boolean onlyInstalled) throws IOException, NoSuchPackageException {
-        List<Dependency> unresolved = new LinkedList<Dependency>();
-        List<PackageId> resolved = new LinkedList<PackageId>();
-        try (RegisteredPackage pkg = open(id)) {
-            if (pkg == null) {
-                throw new NoSuchPackageException().setId(id);
-            }
-            //noinspection resource
-            for (Dependency dep : pkg.getPackage().getDependencies()) {
-                PackageId resolvedId = resolve(dep, onlyInstalled);
-                if (resolvedId == null) {
-                    unresolved.add(dep);
-                } else {
-                    resolved.add(resolvedId);
-                }
-            }
-        }
-
-        return new DependencyReportImpl(id, unresolved.toArray(new Dependency[unresolved.size()]),
-                resolved.toArray(new PackageId[resolved.size()])
-        );
-    }
-
     @Nonnull
     @Override
     public PackageId register(@Nonnull InputStream in, boolean replace) throws IOException, PackageExistsException {
@@ -820,40 +760,28 @@ public class JcrPackageRegistry implemen
         return packRootPaths[0] + getRelativeInstallationPath(id);
     }
 
-    /**
-     * Returns the relative path of this package. please note that since 2.3 this also
-     * includes the version, but never the extension (.zip).
-     *
-     * @param id the package id
-     * @return the relative path of this package
-     * @since 2.2
-     */
-    public String getRelativeInstallationPath(PackageId id) {
-        StringBuilder b = new StringBuilder("/");
-        if (id.getGroup().length() > 0) {
-            b.append(id.getGroup());
-            b.append("/");
-        }
-        b.append(id.getName());
-        String v = id.getVersion().toString();
-        if (v.length() > 0) {
-            b.append("-").append(v);
+    @Override
+    public void installPackage(@Nonnull Session session, @Nonnull RegisteredPackage pkg, @Nonnull ImportOptions opts,
+            boolean extract) throws IOException, PackageException {
+        try (JcrPackage jcrPkg = ((JcrRegisteredPackage) pkg).getJcrPackage()) {
+            if (extract) {
+                jcrPkg.extract(opts);
+            } else {
+                jcrPkg.install(opts);
+            }
+        } catch (RepositoryException e) {
+            throw new IOException(e);
         }
-        return b.toString();
-    }
-
-    /**
-     * Creates a random package id for packages that lack one.
-     * @return a random package id.
-     */
-    private static PackageId createRandomPid() {
-        return new PackageId("temporary", "pack_" + UUID.randomUUID().toString(), (String) null);
     }
 
-    @Nonnull
     @Override
-    public ExecutionPlanBuilder createExecutionPlan() {
-        return new ExecutionPlanBuilderImpl(this);
+    public void uninstallPackage(@Nonnull Session session, @Nonnull RegisteredPackage pkg, @Nonnull ImportOptions opts)
+            throws IOException, PackageException {
+        try (JcrPackage jcrPkg = ((JcrRegisteredPackage) pkg).getJcrPackage()) {
+            jcrPkg.uninstall(opts);
+        } catch (RepositoryException e) {
+            throw new IOException(e);
+        }
     }
 
 }
\ No newline at end of file

Modified: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/PackageTaskImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/PackageTaskImpl.java?rev=1836554&r1=1836553&r2=1836554&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/PackageTaskImpl.java (original)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/impl/PackageTaskImpl.java Tue Jul 24 14:32:12 2018
@@ -20,14 +20,13 @@ import java.io.IOException;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
-import javax.jcr.RepositoryException;
 
 import org.apache.jackrabbit.vault.fs.io.ImportOptions;
 import org.apache.jackrabbit.vault.packaging.DependencyHandling;
-import org.apache.jackrabbit.vault.packaging.JcrPackage;
 import org.apache.jackrabbit.vault.packaging.NoSuchPackageException;
 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.RegisteredPackage;
 import org.slf4j.Logger;
@@ -152,13 +151,9 @@ public class PackageTaskImpl implements
             if (pkg == null) {
                 throw new NoSuchPackageException("No such package: " + id);
             }
-            if (!(pkg instanceof JcrRegisteredPackage)) {
-                throw new PackageException("non jcr packages not supported yet");
-            }
-            try (JcrPackage jcrPkg = ((JcrRegisteredPackage) pkg).getJcrPackage()){
-                jcrPkg.uninstall(opts);
-            } catch (RepositoryException e) {
-                throw new IOException(e);
+            PackageRegistry registry = plan.getRegistry();
+            if (registry instanceof InternalPackageRegistry) {
+              ((InternalPackageRegistry)registry).uninstallPackage(plan.getSession(), pkg, opts);
             }
         }
     }
@@ -179,17 +174,9 @@ public class PackageTaskImpl implements
             if (pkg == null) {
                 throw new NoSuchPackageException("No such package: " + id);
             }
-            if (!(pkg instanceof JcrRegisteredPackage)) {
-                throw new PackageException("non jcr packages not supported yet");
-            }
-            try (JcrPackage jcrPkg = ((JcrRegisteredPackage) pkg).getJcrPackage()){
-                if (extract) {
-                    jcrPkg.extract(opts);
-                } else {
-                    jcrPkg.install(opts);
-                }
-            } catch (RepositoryException e) {
-                throw new IOException(e);
+            PackageRegistry registry = plan.getRegistry();
+            if (registry instanceof InternalPackageRegistry) {
+              ((InternalPackageRegistry)registry).installPackage(plan.getSession(), pkg, opts, extract);
             }
         }
     }

Modified: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/package-info.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/package-info.java?rev=1836554&r1=1836553&r2=1836554&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/package-info.java (original)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/registry/package-info.java Tue Jul 24 14:32:12 2018
@@ -14,5 +14,5 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@org.osgi.annotation.versioning.Version("1.0.0")
+@org.osgi.annotation.versioning.Version("1.1.0")
 package org.apache.jackrabbit.vault.packaging.registry;

Modified: jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/IntegrationTestBase.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/IntegrationTestBase.java?rev=1836554&r1=1836553&r2=1836554&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/IntegrationTestBase.java (original)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/IntegrationTestBase.java Tue Jul 24 14:32:12 2018
@@ -92,6 +92,7 @@ import org.apache.jackrabbit.vault.packa
 import org.apache.jackrabbit.vault.packaging.impl.ActivityLog;
 import org.apache.jackrabbit.vault.packaging.impl.JcrPackageManagerImpl;
 import org.apache.jackrabbit.vault.packaging.impl.ZipVaultPackage;
+import org.apache.jackrabbit.vault.packaging.registry.impl.JcrPackageRegistry;
 import org.apache.jackrabbit.vault.util.Text;
 import org.junit.After;
 import org.junit.AfterClass;
@@ -438,7 +439,7 @@ public class IntegrationTestBase  {
      */
     public String getInstallationPath(PackageId id) {
         // make sure we use the one from the test parameter
-        return packMgr.getRegistry().getInstallationPath(id) + ".zip";
+        return ((JcrPackageRegistry)packMgr.getRegistry()).getInstallationPath(id) + ".zip";
     }
 
     public void assertPackageNodeExists(PackageId id) throws RepositoryException {

Added: jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestFSInstallState.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestFSInstallState.java?rev=1836554&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestFSInstallState.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestFSInstallState.java Tue Jul 24 14:32:12 2018
@@ -0,0 +1,95 @@
+/*
+ * 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.integration;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.jackrabbit.vault.packaging.Dependency;
+import org.apache.jackrabbit.vault.packaging.PackageId;
+import org.apache.jackrabbit.vault.packaging.SubPackageHandling;
+import org.apache.jackrabbit.vault.packaging.registry.impl.FSInstallState;
+import org.apache.jackrabbit.vault.packaging.registry.impl.FSPackageStatus;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test the Package registry interface
+ */
+public class TestFSInstallState {
+
+    private static final PackageId TMP_PACKAGE_ID = new PackageId("my_packages", "tmp", "");
+
+    private static final String TEST_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+            "<registryMetadata packageid=\"my_packages:tmp\" installtime=\"1234\"\n" +
+            "    filepath=\"test.zip\" external=\"true\" packagestatus=\"extracted\">\n" +
+            "    <dependency packageid=\"my_packages:tmp\"/>\n" +
+            "    <subpackage packageid=\"my_packages:tmp\" sphoption=\"ADD\"/>\n" +
+            "</registryMetadata>\n";
+
+    @Test
+    public void testWriteInstallState() throws IOException {
+        File testFile = new File("test.zip");
+        Set<Dependency> deps = new HashSet<>();
+        deps.add(new Dependency(TMP_PACKAGE_ID));
+        Map<PackageId, SubPackageHandling.Option> subs = new HashMap<>();
+        subs.put(TMP_PACKAGE_ID, SubPackageHandling.Option.ADD);
+
+        FSInstallState state = new FSInstallState(TMP_PACKAGE_ID, FSPackageStatus.EXTRACTED)
+                .withFilePath(testFile.toPath())
+                .withExternal(true)
+                .withDependencies(deps)
+                .withSubPackages(subs)
+                .withInstallTime(1234L);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        state.save(out);
+        out.close();
+
+        assertEquals(TEST_XML, out.toString("utf-8"));
+    }
+
+    @Test
+    public void testReadInstallStateNonExistent() throws IOException {
+        FSInstallState state = FSInstallState.fromFile(new File("nonexist.xml"));
+        assertEquals(null, state);
+    }
+    @Test
+    public void testReadInstallState() throws IOException {
+        Set<Dependency> deps = new HashSet<>();
+        deps.add(new Dependency(TMP_PACKAGE_ID));
+        Map<PackageId, SubPackageHandling.Option> subs = new HashMap<>();
+        subs.put(TMP_PACKAGE_ID, SubPackageHandling.Option.ADD);
+
+        FSInstallState state = FSInstallState.fromStream(new ByteArrayInputStream(TEST_XML.getBytes("utf-8")), "test.xml");
+        assertEquals(Paths.get("test.zip"), state.getFilePath());
+        assertEquals(FSPackageStatus.EXTRACTED, state.getStatus());
+        assertEquals(true, state.isExternal());
+        assertEquals(deps, state.getDependencies());
+        assertEquals(subs, state.getSubPackages());
+        assertEquals((Long) 1234L, state.getInstallationTime());
+
+    }
+}
\ No newline at end of file