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 2017/09/15 06:24:39 UTC

svn commit: r1808412 [5/7] - in /jackrabbit/commons/filevault-package-maven-plugin/trunk: ./ it/ it/src/ it/src/test/ it/src/test/java/ it/src/test/java/org/ it/src/test/java/org/apache/ it/src/test/java/org/apache/jackrabbit/ it/src/test/java/org/apac...

Added: jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ImportPackageBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ImportPackageBuilder.java?rev=1808412&view=auto
==============================================================================
--- jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ImportPackageBuilder.java (added)
+++ jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ImportPackageBuilder.java Fri Sep 15 06:24:37 2017
@@ -0,0 +1,484 @@
+/*
+ * 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.filevault.maven.packaging;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+import javax.annotation.Nonnull;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.DependencyResolutionRequiredException;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.util.DirectoryScanner;
+import org.codehaus.plexus.util.StringUtils;
+
+import aQute.bnd.header.Attrs;
+import aQute.bnd.header.Parameters;
+import aQute.bnd.osgi.Analyzer;
+import aQute.bnd.osgi.Clazz;
+import aQute.bnd.osgi.Constants;
+import aQute.bnd.osgi.Descriptors;
+import aQute.bnd.osgi.FileResource;
+import aQute.bnd.osgi.Processor;
+import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
+import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult;
+
+/**
+ * The import package builder is used to analyze the classes and dependencies of the project and calculate the
+ * import-package statement for this package.
+ */
+public class ImportPackageBuilder {
+
+    /**
+     * class file directory
+     */
+    private File classFileDirectory;
+
+    /**
+     * list of class files. initialized during {@link #analyze()}
+     */
+    private List<File> classFiles;
+
+    /**
+     * list of artifacts relevant for analysis
+     */
+    private List<Artifact> artifacts;
+
+    /**
+     * BND analyzer
+     */
+    private Analyzer analyzer;
+
+    /**
+     * The scan result from the fast-classpath analyzer
+     */
+    private ScanResult scanResult;
+
+    /**
+     * artifact-id -> bundle info mapping
+     */
+    private Map<String, BundleInfo> bundles = new HashMap<String, BundleInfo>();
+
+    /**
+     * map of all exported packaged by the bundles.
+     */
+    private Map<String, PackageInfo> exported = new HashMap<String, PackageInfo>();
+
+    /**
+     * map of all classes
+     */
+    private Map<String, ClassInfo> classes = new HashMap<String, ClassInfo>();
+
+    /**
+     * the calculated import parameters.
+     */
+    private Map<String, Attrs> importParameters = Collections.emptyMap();
+
+    /**
+     * Sets the class files directory
+     * @param classes the directory
+     * @return this.
+     */
+    @Nonnull
+    public ImportPackageBuilder withClassFileDirectory(File classes) {
+        classFileDirectory = classes;
+        return this;
+    }
+
+    /**
+     * defines the project from which the artifacts should be loaded.
+     * @param project the maven project
+     * @return this
+     */
+    @Nonnull
+    public ImportPackageBuilder withDependenciesFromProject(@Nonnull MavenProject project) {
+        artifacts = new ArrayList<Artifact>();
+        for (Artifact a : project.getDependencyArtifacts()) {
+            if (!Artifact.SCOPE_PROVIDED.equals(a.getScope()) && !Artifact.SCOPE_RUNTIME.equals(a.getScope())) {
+                continue;
+            }
+            if (!"jar".equals(a.getType())) {
+                continue;
+            }
+            artifacts.add(a);
+        }
+        return this;
+    }
+
+    /**
+     * analyzes the imports
+     * @return this
+     * @throws IOException if an error occurrs.
+     */
+    @Nonnull
+    public ImportPackageBuilder analyze() throws IOException {
+        initClassFiles();
+        initAnalyzer();
+        scanClassPath();
+        scanBundles();
+        scanClasses();
+        calculateImportParameters();
+        return this;
+    }
+
+    /**
+     * returns the import parameter header. only available after {@link #analyze()}
+     * @return the parameters
+     */
+    @Nonnull
+    public Map<String, Attrs> getImportParameters() {
+        return importParameters;
+    }
+
+    /**
+     * generates a package report
+     * @return the report
+     */
+    @Nonnull
+    public String createExportPackageReport() {
+        TreeSet<String> unusedBundles = new TreeSet<String>(bundles.keySet());
+        StringBuilder report = new StringBuilder("Export package report:\n\n");
+        List<String> packages = new ArrayList<String>(exported.keySet());
+        Collections.sort(packages);
+        int pad = 18;
+        for (String packageName : packages) {
+            pad = Math.max(pad, packageName.length());
+        }
+        pad += 2;
+
+        report
+                .append(StringUtils.rightPad("Exported packages", pad))
+                .append(StringUtils.rightPad("Uses", 5))
+                .append(StringUtils.rightPad("Version", 10))
+                .append("Dependency\n");
+        report.append(StringUtils.repeat("-", pad + 30)).append("\n");
+        for (String packageName : packages) {
+            PackageInfo info = exported.get(packageName);
+
+            report.append(StringUtils.rightPad(packageName, pad));
+            report.append(StringUtils.rightPad(String.valueOf(info.usedBy.size()), 5));
+            boolean first = true;
+            for (BundleInfo bInfo : info.bundles.values()) {
+                if (first) {
+                    String version = bInfo.packageVersions.get(packageName);
+                    if (StringUtils.isEmpty(version)) {
+                        version = "0.0.0";
+                    }
+                    report.append(StringUtils.rightPad(version, 10));
+                    report.append(bInfo.getId());
+                    first = false;
+                }
+                if (!info.usedBy.isEmpty()) {
+                    unusedBundles.remove(bInfo.getId());
+                }
+            }
+            if (first) {
+                report.append(StringUtils.rightPad("n/a", 10));
+            }
+            report.append("\n");
+        }
+
+        report.append("\n").append(unusedBundles.size()).append(" unused bundles\n");
+        report.append("------------------------------\n");
+        for (String bundleId : unusedBundles) {
+            report.append(bundleId).append("\n");
+        }
+
+        report.append("\nAuto generated import-package: \n");
+        report.append("------------------------------\n");
+        for (Map.Entry<String, Attrs> e: importParameters.entrySet()) {
+            report.append(e.getKey());
+            try {
+                Processor.printClause(e.getValue(), report);
+            } catch (IOException e1) {
+                throw new IllegalStateException("Internal error while generating report", e1);
+            }
+            report.append("\n");
+        }
+
+        return report.toString();
+    }
+
+    /**
+     * internally scans all the class files.
+     */
+    private void initClassFiles() {
+        if (!classFileDirectory.exists()) {
+            classFiles = Collections.emptyList();
+            return;
+        }
+        DirectoryScanner scanner = new DirectoryScanner();
+        scanner.setBasedir(classFileDirectory);
+        scanner.setIncludes(new String[]{"**/*.class"});
+        scanner.scan();
+        String[] paths = scanner.getIncludedFiles();
+        classFiles = new ArrayList<File>(paths.length);
+        for (String path : paths) {
+            File file = new File(path);
+            if (!file.isAbsolute()) {
+                file = new File(classFileDirectory, path);
+            }
+            classFiles.add(file);
+        }
+    }
+
+    /**
+     * Returns the classloader for the analysis. this should exclude the classes of the maven runtime.
+     *
+     * @throws IOException If an error occurs
+     */
+    private ClassLoader getClassLoader() throws IOException,
+            DependencyResolutionRequiredException {
+        List<URL> classPath = new ArrayList<URL>();
+
+        // add output directory to classpath
+        classPath.add(classFileDirectory.toURI().toURL());
+
+        // add artifacts from project
+        for (Artifact a: artifacts) {
+            classPath.add(a.getFile().toURI().toURL());
+        }
+
+        // use our parent as parent, in order to exclude the maven runtime.
+        return new URLClassLoader(classPath.toArray(new URL[classPath.size()]), this.getClass().getClassLoader().getParent());
+    }
+
+    /**
+     * scans the classpath
+     * @throws IOException if an error occurrs.
+     */
+    private void scanClassPath() throws IOException {
+        try {
+            scanResult = new FastClasspathScanner()
+                    .overrideClassLoaders(getClassLoader())
+                    .scan();
+
+        } catch (Exception e) {
+            throw new IOException("Failed to scan the classpath", e);
+        }
+    }
+
+    /**
+     * initializes the bnd analyzer
+     */
+    private void initAnalyzer() {
+        analyzer = new Analyzer();
+    }
+
+    /**
+     * scans all the bundles and initializes their export packages.
+     * @throws IOException if an error occurrs
+     */
+    private void scanBundles() throws IOException {
+        for (Artifact a : artifacts) {
+            BundleInfo info = new BundleInfo(a);
+            bundles.put(info.getId(), info);
+
+            // update the reverse map
+            for (String pkgName : info.packageVersions.keySet()) {
+                PackageInfo pkg = exported.get(pkgName);
+                if (pkg == null) {
+                    pkg = new PackageInfo(pkgName);
+                    exported.put(pkgName, pkg);
+                }
+                pkg.bundles.put(info.getId(), info);
+            }
+        }
+    }
+
+    /**
+     * Registers the package reference from the given class
+     * @param info the class info that references the package
+     * @param pkgName the package that is referenced
+     */
+    private void registerPackageReference(ClassInfo info, String pkgName) {
+        PackageInfo pkgInfo = exported.get(pkgName);
+        if (pkgInfo == null) {
+            pkgInfo = new PackageInfo(pkgName);
+            exported.put(pkgName, pkgInfo);
+        }
+        info.resolved.put(pkgName, pkgInfo);
+        pkgInfo.usedBy.add(info.getName());
+    }
+
+    /**
+     * scans the classes and resolves them against the bundles.
+     * @throws IOException if an error occurrs.
+     */
+    private void scanClasses() throws IOException {
+        for (File file : classFiles) {
+            try {
+                Clazz clazz = new Clazz(analyzer, file.getPath(), new FileResource(file));
+                clazz.parseClassFile();
+                ClassInfo info = new ClassInfo(clazz);
+                classes.put(info.getName(), info);
+
+                String myPackage = getPackageName(info.getName());
+                for (Descriptors.PackageRef ref : clazz.getReferred()) {
+                    String importPkgName = ref.getFQN();
+                    if (!importPkgName.equals(myPackage)) {
+                        registerPackageReference(info, importPkgName);
+                    }
+                }
+
+                // checking for super classes
+                io.github.lukehutch.fastclasspathscanner.scanner.ClassInfo clzInfo = scanResult.getClassNameToClassInfo().get(clazz.getFQN());
+                if (clzInfo != null) {
+                    for (String name: clzInfo.getNamesOfImplementedInterfaces()) {
+                        registerPackageReference(info, getPackageName(name));
+                    }
+                    for (String name: clzInfo.getNamesOfSuperclasses()) {
+                        registerPackageReference(info, getPackageName(name));
+                    }
+                }
+            } catch (Exception e) {
+                throw new IOException("Error while parsing class: " + file.getPath(), e);
+            }
+        }
+    }
+
+    /**
+     * Returns the package name for the given class name
+     * @param className the class name
+     * @return the package name
+     */
+    private static String getPackageName(String className) {
+        return StringUtils.chomp(className, ".");
+    }
+
+    /**
+     * Calculates returns the import parameter header.
+     */
+    private void calculateImportParameters() {
+        importParameters = new TreeMap<String, Attrs>();
+        for (PackageInfo info : exported.values()) {
+            if (!classFiles.isEmpty() && info.usedBy.isEmpty()) {
+                // skip if not used.
+                continue;
+            }
+            if (info.bundles.isEmpty()) {
+                // skip if no bundle
+                continue;
+            }
+            // get first version
+            BundleInfo bInfo = info.bundles.values().iterator().next();
+            String version = bInfo.packageVersions.get(info.getName());
+            Attrs options = new Attrs();
+            if (!StringUtils.isEmpty(version)) {
+                options.put(Constants.VERSION_ATTRIBUTE, new aQute.bnd.version.VersionRange("@" + version).toString());
+            }
+            importParameters.put(info.getName(), options);
+        }
+    }
+
+
+    private static class PackageInfo {
+
+        private final String name;
+
+        private final Map<String, BundleInfo> bundles = new HashMap<String, BundleInfo>();
+
+        private final Set<String> usedBy = new HashSet<String>();
+
+        private PackageInfo(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
+
+    private static class BundleInfo {
+
+        private final String id;
+
+        private final Map<String, String> packageVersions = new HashMap<String, String>();
+
+        private BundleInfo(Artifact artifact) throws IOException {
+            id = artifact.getId();
+            JarFile jar = new JarFile(artifact.getFile());
+            Manifest manifest = jar.getManifest();
+            String exportPackages = manifest.getMainAttributes().getValue(Constants.EXPORT_PACKAGE);
+            if (exportPackages != null) {
+                for (Map.Entry<String, Attrs> entry : new Parameters(exportPackages).entrySet()) {
+                    Attrs options = entry.getValue();
+                    String version = options.getVersion();
+                    packageVersions.put(entry.getKey(), version == null ? "" : version);
+                }
+            } else {
+                // scan the jar and associate the version
+                Enumeration<JarEntry> entries = jar.entries();
+                while (entries.hasMoreElements()) {
+                    JarEntry e = entries.nextElement();
+                    if (e.isDirectory()) {
+                        continue;
+                    }
+                    String path = e.getName();
+                    if (path.endsWith(".class")) {
+                        // skip internal / impl
+                        if (path.contains("/impl/") || path.contains("/internal/")) {
+                            continue;
+                        }
+                        path = StringUtils.chomp(path, "/");
+                        if (path.charAt(0) == '/') {
+                            path = path.substring(1);
+                        }
+                        String packageName = path.replaceAll("/", ".");
+                        packageVersions.put(packageName, "");
+                    }
+                }
+            }
+            jar.close();
+        }
+
+        public String getId() {
+            return id;
+        }
+    }
+
+    private static class ClassInfo {
+
+        private final Clazz clazz;
+
+        private final Map<String, PackageInfo> resolved = new HashMap<String, PackageInfo>();
+
+        private ClassInfo(Clazz clazz) {
+            this.clazz = clazz;
+        }
+
+        public String getName() {
+            return clazz.getFQN();
+        }
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/IncludeExcludeList.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/IncludeExcludeList.java?rev=1808412&view=auto
==============================================================================
--- jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/IncludeExcludeList.java (added)
+++ jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/IncludeExcludeList.java Fri Sep 15 06:24:37 2017
@@ -0,0 +1,40 @@
+/*
+ * 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.filevault.maven.packaging;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+public class IncludeExcludeList extends ArrayList<String> {
+
+    public boolean add(String entry) {
+        return entry != null && super.add(entry);
+    }
+
+    public void write(PrintWriter pw, String tag) {
+        for (String include: this) {
+            pw.print(Filter.INDENT);
+            pw.print(Filter.INDENT);
+            pw.print('<');
+            pw.print(tag);
+            pw.print(" pattern=\"");
+            pw.print(include);
+            pw.print("\" />");
+            pw.println();
+        }
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/PackageDependency.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/PackageDependency.java?rev=1808412&view=auto
==============================================================================
--- jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/PackageDependency.java (added)
+++ jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/PackageDependency.java Fri Sep 15 06:24:37 2017
@@ -0,0 +1,265 @@
+/*
+ * 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.filevault.maven.packaging;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Implements a package dependency reference.
+ * @since 2.0
+ */
+public class PackageDependency {
+
+    /**
+     * An empty dependency array
+     */
+    public static final PackageDependency[] EMPTY = new PackageDependency[0];
+
+    /**
+     * group id of the dependency
+     */
+    private final String groupId;
+
+    /**
+     * name of the dependency
+     */
+    private final String name;
+
+    /**
+     * Version range of the dependency
+     */
+    private final VersionRange range;
+
+    /**
+     * internal string representation
+     */
+    private final String str;
+
+    /**
+     * Creates a new dependency reference.
+     * @param groupId group id
+     * @param name name
+     * @param range version range
+     */
+    public PackageDependency(String groupId, String name, VersionRange range) {
+        if (groupId.startsWith(PackageId.ETC_PACKAGES_PREFIX)) {
+            groupId = groupId.substring(PackageId.ETC_PACKAGES_PREFIX.length());
+        }
+        this.groupId = groupId;
+        this.name = name;
+        this.range = range == null ? VersionRange.INFINITE : range;
+        StringBuilder b = new StringBuilder();
+        if (groupId.length() > 0 || !VersionRange.INFINITE.equals(this.range)) {
+            b.append(groupId);
+            b.append(":");
+        }
+        b.append(name);
+        if (!VersionRange.INFINITE.equals(this.range)) {
+            b.append(":");
+            b.append(range);
+        }
+        this.str = b.toString();
+    }
+
+    /**
+     * Creates a new dependency to the specified package id
+     * @param id package id.
+     */
+    public PackageDependency(PackageId id) {
+        this(id.getGroup(), id.getName(), new VersionRange(id.getVersion()));
+    }
+
+    /**
+     * Returns the group id of the dependency
+     * @return the gorup id
+     */
+    public String getGroupId() {
+        return groupId;
+    }
+
+    /**
+     * Returns the name of the dependency
+     * @return the name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns the version range
+     * @return the version range
+     */
+    public VersionRange getRange() {
+        return range;
+    }
+
+    /**
+     * Returns the installation path of this dependency
+     * @return the installation path;
+     */
+    public String getPath() {
+        StringBuilder b = new StringBuilder();
+        if (groupId.length() > 0) {
+            if (groupId.charAt(0) != '/') {
+                b.append(PackageId.ETC_PACKAGES_PREFIX);
+            }
+            b.append(groupId);
+            b.append("/");
+        }
+        b.append(name);
+        return b.toString();
+    }
+
+    /**
+     * Checks if the given package id matches this dependency specification.
+     * @param id the package id
+     * @return <code>true</code> if matches
+     */
+    public boolean matches(PackageId id) {
+        return groupId.equals(id.getGroup())
+                && name.equals(id.getName())
+                && range.isInRange(id.getVersion());
+    }
+
+    /**
+     * Returns a dependency from a string. if the given id is null or an
+     * empty string, <code>null</code> is returned.
+     * @param str the string
+     * @return the dependency
+     */
+    public static PackageDependency fromString(String str) {
+        if (str == null || str.length() == 0) {
+            return null;
+        }
+        String[] segs = str.split(":");
+        String name;
+        String groupId = "";
+        String range = null;
+        if (segs.length == 1) {
+            name = segs[0];
+            // be backward compatible, respect group in name
+            int idx = name.lastIndexOf('/');
+            if (idx >= 0) {
+                groupId = name.substring(0, idx);
+                name = name.substring(idx + 1);
+            }
+        } else if (segs.length == 2) {
+            groupId = segs[0];
+            name = segs[1];
+            boolean isVersion = true;
+            if (name.length() > 0) {
+                char c = name.charAt(0);
+                isVersion = Character.isDigit(c) || c == '[' || c == ')';
+            }
+            // be backward compatible, respect group in name
+            int idx = name.lastIndexOf('/');
+            if (idx >= 0 && groupId.length() == 0) {
+                groupId = name.substring(0, idx);
+                name = name.substring(idx + 1);
+            } else if ((idx = groupId.lastIndexOf('/')) >=0 && isVersion) {
+                groupId = segs[0].substring(0, idx);
+                name = segs[0].substring(idx + 1);
+                range = segs[1];
+            }
+        } else {
+            groupId = segs[0];
+            name = segs[1];
+            range = segs[2];
+        }
+        return new PackageDependency(groupId, name, range == null ? null : VersionRange.fromString(range));
+    }
+
+    /**
+     * Parses a string serialization of dependency references generated by
+     * {@link #toString(PackageDependency...)})}
+     *
+     * @param str serialized string
+     * @return array of dependency references
+     */
+    public static PackageDependency[] parse(String str) {
+        List<PackageDependency> deps = new ArrayList<PackageDependency>();
+        boolean inRange = false;
+        int start = 0;
+        boolean wasSeg = false;
+        for (int i=0; i<str.length(); i++) {
+            char c = str.charAt(i);
+            if (c == ',') {
+                if (!inRange) {
+                    deps.add(PackageDependency.fromString(str.substring(start, i)));
+                    start = i + 1;
+                }
+            } else if (c == '[' || c == '(') {
+                if (wasSeg) {
+                    inRange = true;
+                }
+            } else if (c == ']' || c == ')') {
+                inRange = false;
+            }
+            wasSeg = c == ':';
+        }
+        if (start < str.length()) {
+            deps.add(PackageDependency.fromString(str.substring(start)));
+        }
+        return deps.toArray(new PackageDependency[deps.size()]);
+    }
+    
+    /**
+     * Returns dependencies from the given strings.
+     * @param str the strings
+     * @return the dependencies
+     */
+    public static PackageDependency[] fromString(String ... str) {
+        PackageDependency[] ret = new PackageDependency[str.length];
+        for (int i=0; i<str.length;i++) {
+            ret[i] = PackageDependency.fromString(str[i]);
+        }
+        return ret;
+    }
+
+    /**
+     * Returns a string representation from given dependencies.
+     * @param deps the dependencies
+     * @return the strings
+     */
+    public static String toString(PackageDependency ... deps) {
+        String delim = "";
+        StringBuilder b = new StringBuilder();
+        for (PackageDependency dep: deps) {
+            b.append(delim).append(dep);
+            delim=",";
+        }
+        return b.toString();
+    }
+
+    @Override
+    public String toString() {
+        return str;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return this == obj ||
+                obj instanceof PackageDependency && str.equals(obj.toString());
+    }
+
+    @Override
+    public int hashCode() {
+        return str.hashCode();
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/PackageId.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/PackageId.java?rev=1808412&view=auto
==============================================================================
--- jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/PackageId.java (added)
+++ jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/PackageId.java Fri Sep 15 06:24:37 2017
@@ -0,0 +1,413 @@
+/*
+ * 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.filevault.maven.packaging;
+
+/**
+ * <code>PackageId</code> provides the basic metrics for identifying a package.
+ * A package id consists of a group id, a name and a version.
+ * the group is a relative path, eg: "day/cq5/hotfix", the name and the version
+ * can be of any format.
+ */
+public class PackageId implements Comparable<PackageId> {
+
+    public static final String ETC_PACKAGES = "/etc/packages";
+
+    public static final String ETC_PACKAGES_PREFIX = "/etc/packages/";
+
+    public static final PackageId[] EMPTY = new PackageId[0];
+    
+    private final String group;
+
+    private final String name;
+
+    private final Version version;
+
+    private final String str;
+
+    private final boolean fromPath;
+
+    /**
+     * Creates a new package id
+     * @param path path of the package
+     */
+    public PackageId(String path) {
+        fromPath = true;
+        path = path.trim();
+        int idx = path.lastIndexOf('.');
+        if (idx > 0) {
+            String ext = path.substring(idx);
+            if (ext.equalsIgnoreCase(".zip") || ext.equalsIgnoreCase(".jar")) {
+                path = path.substring(0, idx);
+            }
+        }
+        idx = path.lastIndexOf('/');
+        String name;
+        if (idx < 0) {
+            name = path;
+            this.group = "";
+        } else {
+            name = path.substring(idx + 1);
+            String grp = path.substring(0, idx);
+            if (grp.equals(ETC_PACKAGES)) {
+                grp = "";
+            } else if (grp.startsWith(ETC_PACKAGES_PREFIX)) {
+                grp = grp.substring(ETC_PACKAGES_PREFIX.length());
+            } else if (grp.startsWith("/")) {
+                grp = grp.substring(1);
+            }
+            this.group = grp;
+        }
+        // check if name contains a version
+        String[] segs = name.split("-");
+        int i=segs.length-1;
+        while (i>0) {
+            try {
+                // accept numbers < 1000 (hotfix case)
+                if (Integer.parseInt(segs[i]) >= 1000) {
+                    break;
+                }
+            } catch (NumberFormatException e) {
+                // ignore
+            }
+            // check if starts with a letter'
+            if (Character.isJavaIdentifierStart(segs[i].charAt(0))) {
+                // then need a digit
+                if (segs[i].length() == 1 || !Character.isDigit(segs[i].charAt(1)) && !segs[i].equals("SNAPSHOT")) {
+                    break;
+                }
+            }
+            i--;
+        }
+        if (i == segs.length-1) {
+            this.name = name;
+            version = Version.EMPTY;
+        } else {
+            StringBuilder str = new StringBuilder();
+            for (int j = 0; j<= i; j++) {
+                if (j > 0) {
+                    str.append('-');
+                }
+                str.append(segs[j]);
+            }
+            this.name = str.toString();
+            str.setLength(0);
+            for (int j = i+1; j<segs.length; j++) {
+                if (j > i+1) {
+                    str.append('-');
+                }
+                str.append(segs[j]);
+            }
+            this.version = Version.create(str.toString());
+        }
+        this.str = getString(group, this.name, version);
+
+    }
+    /**
+     * Creates a new package id
+     * @param path path of the package
+     * @param version version of the package
+     */
+    public PackageId(String path, String version) {
+        this(path, Version.create(version));
+    }
+
+    /**
+     * Creates a new package id
+     * @param path path of the package
+     * @param version version of the package
+     */
+    public PackageId(String path, Version version) {
+        fromPath = true;
+        path = path.trim();
+        int idx = path.lastIndexOf('.');
+        if (idx > 0) {
+            String ext = path.substring(idx);
+            if (ext.equalsIgnoreCase(".zip") || ext.equalsIgnoreCase(".jar")) {
+                path = path.substring(0, idx);
+            }
+        }
+        if (version != null && path.endsWith('-'+version.toString())) {
+            path = path.substring(0, path.length() - version.toString().length() - 1);
+        }
+        idx = path.lastIndexOf('/');
+        if (idx < 0) {
+            this.name = path;
+            this.group = "";
+        } else {
+            this.name = path.substring(idx + 1);
+            String grp = path.substring(0, idx);
+            if (grp.equals(ETC_PACKAGES)) {
+                grp = "";
+            } else if (grp.startsWith(ETC_PACKAGES_PREFIX)) {
+                grp = grp.substring(ETC_PACKAGES_PREFIX.length());
+            } else if (grp.startsWith("/")) {
+                grp = grp.substring(1);
+            }
+            this.group = grp;
+        }
+        // sanitize version
+        if (version == null || version.toString().length() == 0) {
+            version = Version.EMPTY;
+        }
+        this.version = version;
+        this.str = getString(group, name, version);
+    }
+
+    /**
+     * Creates a new package id
+     * @param group group id
+     * @param name name
+     * @param version version
+     */
+    public PackageId(String group, String name, String version) {
+        this(group, name, Version.create(version));
+    }
+
+    /**
+     * Creates a new package id
+     * @param group group id
+     * @param name name
+     * @param version version
+     */
+    public PackageId(String group, String name, Version version) {
+        fromPath = false;
+        // validate group
+        if (group.equals(ETC_PACKAGES)) {
+            group = "";
+        } else if (group.startsWith(ETC_PACKAGES_PREFIX)) {
+            group = group.substring(ETC_PACKAGES_PREFIX.length());
+        } else if (group.startsWith("/")) {
+            group = group.substring(1);
+        }
+        this.group = group;
+        this.name = name;
+        this.version = version == null ? Version.EMPTY : version;
+        this.str = getString(this.group, name, this.version);
+    }
+
+    /**
+     * Returns a package id from a id string. if the given id is null or an
+     * empty string, <code>null</code> is returned.
+     * @param str the string
+     * @return the package id
+     */
+    public static PackageId fromString(String str) {
+        if (str == null || str.length() == 0) {
+            return null;
+        }
+        String[] segs = str.split(":");
+        if (segs.length == 1) {
+            return new PackageId("", segs[0], "");
+        } else if (segs.length == 2) {
+            return new PackageId(segs[0], segs[1], "");
+        } else {
+            return new PackageId(segs[0], segs[1], segs[2]);
+        }
+    }
+
+    /**
+     * Returns an array of package id from strings
+     * @param str the strings
+     * @return the array of package ids
+     */
+    public static PackageId[] fromString(String ... str) {
+        PackageId[] ret = new PackageId[str.length];
+        for (int i=0; i<str.length; i++) {
+            ret[i] = PackageId.fromString(str[i]);
+        }
+        return ret;
+    }
+
+    /**
+     * Creates a comma separated list of id strings.
+     * @param packs the ids
+     * @return the string
+     */
+    public static String toString(PackageId ... packs) {
+        String delim = "";
+        StringBuilder b = new StringBuilder();
+        for (PackageId pack: packs) {
+            b.append(delim).append(pack);
+            delim=",";
+        }
+        return b.toString();
+    }
+
+    /**
+     * Returns the path of this package
+     * @return the path of this package
+     * @deprecated use {@link #getInstallationPath()} instead
+     */
+    public String getPath() {
+        return getInstallationPath();
+    }
+
+    /**
+     * Checks if this definition was constructed from a path, rather from a
+     * group and name.
+     * @return <code>true</code> if constructed from path.
+     *
+     * @since 2.2.26
+     */
+    public boolean isFromPath() {
+        return fromPath;
+    }
+
+    /**
+     * Returns the path of this package. please note that since 2.3 this also
+     * includes the version, but never the extension (.zip).
+     *
+     * @return the path of this package
+     * @since 2.2
+     */
+    public String getInstallationPath() {
+        StringBuilder b = new StringBuilder(ETC_PACKAGES_PREFIX);
+        if (group.length() > 0) {
+            b.append(group);
+            b.append("/");
+        }
+        b.append(name);
+        if (version.toString().length() > 0) {
+            b.append("-").append(version);
+        }
+        return b.toString();
+    }
+
+    /**
+     * Returns the group id of this package
+     * @return the group id;
+     * @deprecated use {@link #getGroup()} instead.
+     */
+    public String getGroupId() {
+        return group;
+    }
+
+    /**
+     * Returns the group id of this package
+     * @return the group id;
+     * @since 2.2
+     */
+    public String getGroup() {
+        return group;
+    }
+
+    /**
+     * Returns the name of this package which is the last segment of the path.
+     * @return the name of this package.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns the version of this package or and empty string if n/a.
+     * @return the version of this package
+     * @since 2.0
+     */
+    public String getVersionString() {
+        return version.toString();
+    }
+
+    /**
+     * Returns a download name in the form
+     * <code>name [ "-" version ] ".zip"</code>
+     * @return the download name
+     * @since 2.0
+     */
+    public String getDownloadName() {
+        StringBuilder str = new StringBuilder(name);
+        if (version.toString().length() > 0) {
+            str.append("-").append(version);
+        }
+        str.append(".zip");
+        return str.toString();
+    }
+
+    /**
+     * Returns the version of this package or <code>null</code> if n/a.
+     * @return the version of this package
+     */
+    public Version getVersion() {
+        return version;
+    }
+
+    /**
+     * Returns a string representation of this id
+     */
+    @Override
+    public String toString() {
+        return str;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return this == o ||
+                o instanceof PackageId && str.equals(o.toString());
+
+    }
+
+    @Override
+    public int hashCode() {
+        return str.hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     *  
+     * Compares this id with the given one.
+     */
+    public int compareTo(PackageId o) {
+        int comp = group.compareTo(o.getGroup());
+        if (comp != 0) {
+            return comp;
+        }
+        comp = name.compareTo(o.getName());
+        if (comp != 0) {
+            return comp;
+        }
+        return version.compareTo(o.getVersion());
+    }
+
+    /**
+     * Internally get the string representation, colon separated.
+     * @param group group name
+     * @param name name
+     * @param version version
+     * @return string version
+     */
+    private static String getString(String group, String name, Version version) {
+        return getString(group, name, version == null ? "" : version.toString());
+    }
+
+    /**
+     * Internally get the string representation, colon separated.
+     * @param group group name
+     * @param name name
+     * @param version version
+     * @return string version
+     */
+    private static String getString(String group, String name, String version) {
+        StringBuilder b = new StringBuilder();
+        b.append(group).append(':');
+        b.append(name);
+        if (version.length() > 0) {
+            b.append(':').append(version);
+        }
+        return b.toString();
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/SubPackage.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/SubPackage.java?rev=1808412&view=auto
==============================================================================
--- jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/SubPackage.java (added)
+++ jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/SubPackage.java Fri Sep 15 06:24:37 2017
@@ -0,0 +1,158 @@
+/*
+ * 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.filevault.maven.packaging;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
+import org.apache.maven.project.MavenProject;
+
+import org.apache.jackrabbit.filevault.maven.packaging.impl.StringFilterSet;
+
+/**
+ * The <code>Embedded</code> class represents an embedded artifact dependency
+ * from the project descriptor. Such an embedding is declared in
+ * <code>&lt;embedded></code> elements inside the list style
+ * <code>&lt;embeddeds></code> element as follows:
+ *
+ * <pre>
+ * &lt;embedded&gt;
+ * 	   &lt;groupId&gt;artifact.groupId.pattern&lt;/groupId&gt;
+ * 	   &lt;artifactId&gt;artifact.artifactId.pattern&lt;/artifactId&gt;
+ * 	   &lt;scope&gt;compile&lt;/scope&gt;
+ *     &lt;type&gt;jar&lt;/type&gt;
+ *     &lt;classifier&gt;sources&lt;/classifier&gt;
+ * 	   &lt;filter&gt;true&lt;/filter&gt;
+ * &lt;/embedded&gt;
+ * </pre>
+ *
+ * @since 0.8
+ */
+public class SubPackage {
+
+    /**
+     * A group filter string, consisted of one or several comma separated patterns.
+     * @parameter type="String"
+     */
+    private final StringFilterSet groupId = new StringFilterSet();
+
+    /**
+     * A artifact filter string, consisted of one or several comma separated patterns.
+     * @parameter type="String"
+     */
+    private final StringFilterSet artifactId = new StringFilterSet();
+
+    /**
+     * @parameter
+     */
+    private ScopeArtifactFilter scope;
+
+    /**
+     * @parameter
+     */
+    private String type;
+
+    /**
+     * @parameter
+     */
+    private String classifier;
+
+    /**
+     * If <code>true</code> a filter entry will be generated for all embedded artifacts.
+     * @parameter
+     */
+    private boolean filter;
+
+    /**
+     * @parameter
+     */
+    private boolean excludeTransitive;
+
+    public void setGroupId(String groupId) {
+        this.groupId.addEntries(groupId);
+    }
+
+    public void setArtifactId(String artifactId) {
+        this.artifactId.addEntries(artifactId);
+    }
+
+    public void setScope(String scope) {
+        this.scope = new ScopeArtifactFilter(scope);
+    }
+
+    public void setAddFilter(boolean filter) {
+        this.filter = filter;
+    }
+
+    public boolean isFilter() {
+        return filter;
+    }
+
+    public void setExcludeTransitive(boolean excludeTransitive) {
+        this.excludeTransitive = excludeTransitive;
+    }
+
+    public boolean isExcludeTransitive() {
+        return excludeTransitive;
+    }
+
+    public List<Artifact> getMatchingArtifacts(final MavenProject project) {
+
+        // get artifacts depending on whether we exclude transitives or not
+        final Set deps;
+        if (excludeTransitive) {
+            // only direct dependencies, transitives excluded
+            deps = project.getDependencyArtifacts();
+        } else {
+            // all dependencies, transitives included
+            deps = project.getArtifacts();
+        }
+
+        final List<Artifact> matches = new ArrayList<Artifact>();
+        for (Object dep : deps) {
+            final Artifact artifact = (Artifact) dep;
+            if (groupId.contains(artifact.getGroupId())
+                    && artifactId.contains(artifact.getArtifactId())
+                    && (scope == null || scope.include(artifact))
+                    && (type == null || type.equals(artifact.getType()))
+                    && (classifier == null || classifier.equals(artifact.getClassifier()))) {
+                matches.add(artifact);
+            }
+        }
+        return matches;
+    }
+
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append("Sub Packages: ");
+        if (groupId != null) {
+            builder.append("groupId=").append(groupId).append(",");
+        }
+        if (artifactId != null) {
+            builder.append("artifactId=").append(artifactId).append(",");
+        }
+        if (scope != null) {
+            builder.append("scope=").append(scope).append(",");
+        }
+        builder.append("filter=").append(filter);
+        builder.append(",excludeTransitive=").append(excludeTransitive);
+        return builder.toString();
+    }
+}

Added: jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/VaultMojo.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/VaultMojo.java?rev=1808412&view=auto
==============================================================================
--- jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/VaultMojo.java (added)
+++ jackrabbit/commons/filevault-package-maven-plugin/trunk/plugin/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/VaultMojo.java Fri Sep 15 06:24:37 2017
@@ -0,0 +1,854 @@
+/*
+ * 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.filevault.maven.packaging;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import javax.annotation.Nonnull;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.maven.archiver.ManifestConfiguration;
+import org.apache.maven.archiver.MavenArchiveConfiguration;
+import org.apache.maven.archiver.MavenArchiver;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.codehaus.plexus.archiver.ArchiveEntry;
+import org.codehaus.plexus.archiver.FileSet;
+import org.codehaus.plexus.util.FileUtils;
+import org.codehaus.plexus.util.IOUtil;
+import org.codehaus.plexus.util.StringUtils;
+
+import aQute.bnd.header.Attrs;
+import aQute.bnd.header.Parameters;
+import aQute.bnd.osgi.Processor;
+import org.apache.jackrabbit.filevault.maven.packaging.impl.DefaultWorkspaceFilter;
+import org.apache.jackrabbit.filevault.maven.packaging.impl.PackageType;
+import org.apache.jackrabbit.filevault.maven.packaging.impl.PathFilterSet;
+import org.apache.jackrabbit.filevault.maven.packaging.impl.PlatformNameFormat;
+
+import static org.codehaus.plexus.archiver.util.DefaultFileSet.fileSet;
+
+/**
+ * Build a content package.
+ */
+@Mojo(
+        name = "package",
+        defaultPhase = LifecyclePhase.PACKAGE,
+        requiresDependencyResolution = ResolutionScope.COMPILE
+)
+public class VaultMojo extends AbstractEmbeddedsMojo {
+
+    private static final String JCR_ROOT = "jcr_root/";
+
+    private static final String VAULT_DIR = "META-INF/vault";
+
+    public static final String PROPERTIES_FILE = VAULT_DIR + "/properties.xml";
+
+    public static final String FILTER_FILE = VAULT_DIR + "/filter.xml";
+
+    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
+
+    private static final String PACKAGE_TYPE = "zip";
+
+    private static final String PACKAGE_EXT = "." + PACKAGE_TYPE;
+
+    public static final String MF_KEY_PACKAGE_TYPE = "Content-Package-Type";
+
+    public static final String MF_KEY_PACKAGE_ID = "Content-Package-Id";
+
+    private static final String MF_KEY_PACKAGE_DEPENDENCIES = "Content-Package-Dependencies";
+
+    public static final String MF_KEY_PACKAGE_ROOTS = "Content-Package-Roots";
+
+    private static final String MF_KEY_PACKAGE_DESC = "Content-Package-Description";
+
+    private static final String MF_KEY_IMPORT_PACKAGE = "Import-Package";
+
+    @Component
+    private ArtifactHandlerManager artifactHandlerManager;
+
+    /**
+     * The directory containing the content to be packaged up into the content
+     * package.
+     */
+    @Parameter(
+            defaultValue = "${project.build.outputDirectory}",
+            required = true)
+    private File builtContentDirectory;
+
+    /**
+     * The directory containing the compiled classes to use to import analysis.
+     */
+    @Parameter(
+            defaultValue = "${project.build.outputDirectory}",
+            required = true)
+    private File classesDirectory;
+
+    /**
+     * The name of the generated package ZIP file without the ".zip" file
+     * extension.
+     */
+    @Parameter(
+            property = "vault.finalName",
+            defaultValue = "${project.build.finalName}",
+            required = true)
+    private String finalName;
+
+    /**
+     * Directory in which the built content package will be output.
+     */
+    @Parameter(
+            defaultValue="${project.build.directory}",
+            required = true)
+    private File outputDirectory;
+
+    /**
+     * Optional file that specifies the source of the workspace filter. The filters specified in the configuration
+     * and injected via emebedds or subpackages are merged into it.
+     */
+    @Parameter
+    private File filterSource;
+
+    /**
+     * The directory containing the content to be packaged up into the content
+     * package.
+     */
+    @Parameter(
+            defaultValue = "${project.build.directory}/vault-work",
+            required = true)
+    private File workDirectory;
+
+    /**
+     * The archive configuration to use. See <a
+     * href="http://maven.apache.org/shared/maven-archiver/index.html">the
+     * documentation for Maven Archiver</a>.
+     */
+    @Parameter
+    private MavenArchiveConfiguration archive;
+
+    /**
+     * Adds a path prefix to all resources useful for shallower source trees.
+     */
+    @Parameter(property = "vault.prefix")
+    private String prefix;
+
+    /**
+     * The groupId used for the generated content package. This will be part of
+     * the target installation path of the content package.
+     */
+    @Parameter(
+            property = "vault.group",
+            defaultValue="${project.groupId}",
+            required = true)
+    private String group;
+
+    /**
+     * The name of the content package
+     */
+    @Parameter(
+            property = "vault.name",
+            defaultValue="${project.artifactId}",
+            required = true)
+    private String name;
+
+    /**
+     * The version of the content package.
+     */
+    @Parameter(
+            property = "vault.version",
+            defaultValue = "${project.version}",
+            required = true)
+    private String version;
+
+    /**
+     * Defines whether the package requires root. This will become the
+     * <code>requiresRoot</code> property of the properties.xml file.
+     */
+    @Parameter(
+            property = "vault.requiresRoot",
+            defaultValue="false",
+            required = true)
+    private boolean requiresRoot;
+
+    /**
+     * Defines whether the package is allowed to contain index definitions. This will become the
+     * <code>allowIndexDefinitions</code> property of the properties.xml file.
+     */
+    @Parameter(
+            property = "vault.allowIndexDefinitions",
+            defaultValue="false",
+            required = true)
+    private boolean allowIndexDefinitions;
+
+    /**
+     * Controls if errors during dependency validation should fail the build.
+     */
+    @Parameter(
+            property = "vault.failOnDependencyErrors",
+            defaultValue="true",
+            required = true)
+    private boolean failOnDependencyErrors;
+
+    /**
+     * Defines the path under which the embedded bundles are placed. defaults to '/apps/bundles/install'
+     * @since 0.0.6
+     */
+    @Parameter(property = "vault.embeddedTarget")
+    private String embeddedTarget;
+
+    /**
+     * Defines the content package type. this is either 'application', 'content', 'container' or 'mixed'.
+     * If omitted, it is calculated automatically based on filter definitions. certain package types imply restrictions,
+     * for example, 'application' and 'content' packages are not allowed to contain sub packages or embedded bundles.
+     *
+     * @since 0.5.12
+     */
+    @Parameter(property = "vault.packageType")
+    private PackageType packageType;
+
+    /**
+     * Sets the package type.
+     * @param type the string representation of the package type
+     * @throws MojoFailureException if an error occurrs
+     */
+    public void setPackageType(String type) throws MojoFailureException {
+        try {
+            packageType = PackageType.valueOf(type.toUpperCase());
+        } catch (IllegalArgumentException e) {
+            throw new MojoFailureException("Invalid package type specified: " + type +".\n" +
+                    "Must be empty or one of 'application', 'content', 'container', 'mixed'");
+        }
+
+    }
+
+    /**
+     * Defines additional bundle dependency via the osgi import-package entry in the manifest.
+     * @since 0.5.12
+     */
+    @Parameter(property = "vault.importPackage")
+    private String importPackage;
+
+    /**
+     * Defines the content of the filter.xml file
+     */
+    @Parameter
+    private final DefaultWorkspaceFilter filters = new DefaultWorkspaceFilter();
+
+    /**
+     * Defines the list of dependencies
+     */
+    @Parameter
+    private Dependency[] dependencies = new Dependency[0];
+
+    /**
+     * Defines the packages that define the repository structure
+     */
+    @Parameter
+    private Dependency[] repositoryStructurePackages = new Dependency[0];
+
+    /**
+     * computed dependency string
+     * @see #computeDependencies()
+     */
+    private String dependenciesString;
+
+    /**
+     * Defines the list of sub packages.
+     */
+    @Parameter
+    private SubPackage[] subPackages = new SubPackage[0];
+
+    /**
+     * File to store the generated manifest snippet.
+     */
+    @Parameter(property = "vault.generatedImportPackage", defaultValue = "${project.build.directory}/vault-generated-import.txt")
+    private File generatedImportPackage;
+
+    /**
+     * Specifies additional properties to be set in the properties.xml file.
+     * These properties cannot overwrite the following predefined properties:
+     * <p>
+     * <table>
+     * <tr><td>group</td><td>Use <i>group</i> parameter to set</td></tr>
+     * <tr><td>name</td><td>Use <i>name</i> parameter to set</td></tr>
+     * <tr><td>version</td><td>Use <i>version</i> parameter to set</td></tr>
+     * <tr><td>groupId</td><td><i>groupId</i> of the Maven project descriptor</td></tr>
+     * <tr><td>artifactId</td><td><i>artifactId</i> of the Maven project descriptor</td></tr>
+     * <tr><td>dependencies</td><td>Use <i>dependencies</i> parameter to set</td></tr>
+     * <tr><td>createdBy</td><td>The value of the <i>user.name</i> system property</td></tr>
+     * <tr><td>created</td><td>The current system time</td></tr>
+     * <tr><td>requiresRoot</td><td>Use <i>requiresRoot</i> parameter to set</td></tr>
+     * <tr><td>allowIndexDefinitions</td><td>Use <i>allowIndexDefinitions</i> parameter to set</td></tr>
+     * <tr><td>packagePath</td><td>Automatically generated from the group and package name</td></tr>
+     * <tr><td>packageType</td><td>Set via the package type parameter</td></tr>
+     * </table>
+     */
+    @Parameter
+    private final Properties properties = new Properties();
+
+    /**
+     * Creates a fileset for the archiver
+     * @param directory the directory
+     * @param prefix the prefix
+     * @return the fileset
+     */
+    @Nonnull
+    private FileSet createFileSet(@Nonnull File directory, @Nonnull String prefix) {
+        return fileSet(directory).prefixed(prefix).includeExclude(null, null).includeEmptyDirs(true);
+    }
+
+    /**
+     * Executes this mojo
+     */
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+        if (prefix == null) {
+            prefix = "";
+        } else if (!prefix.endsWith("/")) {
+            prefix += "/";
+        }
+
+        final File vaultDir = new File(workDirectory, VAULT_DIR);
+        final File finalFile = new File(outputDirectory, finalName + PACKAGE_EXT);
+
+        try {
+            vaultDir.mkdirs();
+
+            Map<String, File> embeddedFiles = copyEmbeddeds();
+            embeddedFiles.putAll(copySubPackages());
+
+            File filterFile = new File(vaultDir, "filter.xml");
+
+            if (filterSource == null || !filterSource.exists()) {
+                // ignore filtersource
+                DefaultWorkspaceFilter oldFilter;
+                if (filterFile.exists()) {
+                    oldFilter = new DefaultWorkspaceFilter();
+                    oldFilter.load(filterFile);
+
+                    // don't write filter if same
+                    if (filters.getSourceAsString().equals(oldFilter.getSourceAsString())) {
+                        filterFile = null;
+                    } else {
+                        filterFile = new File(vaultDir, "filter-plugin-generated.xml");
+                    }
+                } else {
+                    if (filters.getFilterSets().isEmpty() && prefix.length() > 0) {
+                        addWorkspaceFilter(prefix);
+                    }
+                }
+            } else {
+                getLog().info("Merging " + filterSource.getPath() + " with inlined filter specifications.");
+                DefaultWorkspaceFilter oldFilter = new DefaultWorkspaceFilter();
+                oldFilter.load(filterSource);
+                oldFilter.merge(filters);
+                filters.getFilterSets().clear();
+                filters.getFilterSets().addAll(oldFilter.getFilterSets());
+                if (filters.getFilterSets().isEmpty() && prefix.length() > 0) {
+                    addWorkspaceFilter(prefix);
+                }
+            }
+
+            if (filterFile != null) {
+                FileUtils.fileWrite(filterFile.getAbsolutePath(), filters.getSourceAsString());
+            }
+
+            validatePackageType();
+            computeImportPackage();
+            computeDependencies();
+            if (packageType == PackageType.APPLICATION) {
+                validateDependencies();
+            } else {
+                getLog().info("Ignoring dependency validation due to non-application package type: " + packageType);
+            }
+
+            final Properties vaultProperties = computeProperties();
+            final FileOutputStream fos = new FileOutputStream(new File(vaultDir, "properties.xml"));
+            vaultProperties.storeToXML(fos, project.getName());
+
+            copyFile("/vault/config.xml", new File(vaultDir, "config.xml"));
+            copyFile("/vault/settings.xml", new File(vaultDir, "settings.xml"));
+
+            ContentPackageArchiver contentPackageArchiver = new ContentPackageArchiver();
+            contentPackageArchiver.setIncludeEmptyDirs(true);
+            contentPackageArchiver.addDirectory(workDirectory);
+
+            // include content from build only if it exists
+            if (builtContentDirectory.exists()) {
+                // See GRANITE-16348
+                // we want to build a list of all the root directories in the order they were specified in the filter
+                // but ignore the roots that don't point to a directory
+                List<PathFilterSet> filterSets = filters.getFilterSets();
+                if (filterSets.isEmpty()) {
+                    contentPackageArchiver.addFileSet(createFileSet(builtContentDirectory, FileUtils.normalize(JCR_ROOT + prefix)));
+                } else {
+                    for (PathFilterSet filterSet : filterSets) {
+                        String relPath = PlatformNameFormat.getPlatformPath(filterSet.getRoot());
+                        String rootPath = FileUtils.normalize(JCR_ROOT + prefix + relPath);
+
+                        // CQ-4204625 skip embedded files, will be added later in the proper way
+                        if (embeddedFiles.containsKey(rootPath)) {
+                            continue;
+                        }
+
+                        // check for full coverage aggregate
+                        File fullCoverage = new File(builtContentDirectory, relPath + ".xml");
+                        if (fullCoverage.isFile()) {
+                            rootPath = FileUtils.normalize(JCR_ROOT + prefix + relPath + ".xml");
+                            contentPackageArchiver.addFile(fullCoverage, rootPath);
+                            continue;
+                        }
+
+                        File rootDirectory = new File(builtContentDirectory, relPath);
+
+                        // traverse the ancestors until we find a existing directory (see CQ-4204625)
+                        while ((!rootDirectory.exists() || !rootDirectory.isDirectory())
+                                && !builtContentDirectory.equals(rootDirectory)) {
+                            rootDirectory = rootDirectory.getParentFile();
+                            relPath = StringUtils.chomp(relPath, "/");
+                        }
+
+                        if (!builtContentDirectory.equals(rootDirectory)) {
+                            rootPath = FileUtils.normalize(JCR_ROOT + prefix + relPath);
+                            contentPackageArchiver.addFileSet(createFileSet(rootDirectory, rootPath + "/"));
+                        }
+                    }
+                }
+            }
+
+            for (Map.Entry<String, File> entry : embeddedFiles.entrySet()) {
+                contentPackageArchiver.addFile(entry.getValue(), entry.getKey());
+            }
+
+            //NPR-14102 - Automated check for index definition
+            if (!allowIndexDefinitions) {
+                FileValidator fileValidator = new FileValidator();
+                getLog().info("Scanning files for oak index definitions.");
+                for (ArchiveEntry entry: contentPackageArchiver.getFiles().values()) {
+                    if (entry.getType() == ArchiveEntry.FILE) {
+                        InputStream in = null;
+                        try {
+                            in = entry.getInputStream();
+                            fileValidator.lookupIndexDefinitionInArtifact(in, entry.getName());
+                        } finally {
+                            IOUtils.closeQuietly(in);
+                        }
+                    }
+                }
+                if (fileValidator.isContainingIndexDef) {
+                    getLog().error(fileValidator.getMessageWithPathsOfIndexDef());
+                    throw new MojoExecutionException("Package should not contain index definitions, because 'allowIndexDefinitions=false'.");
+                }
+            }
+
+            MavenArchiver mavenArchiver = new MavenArchiver();
+            mavenArchiver.setArchiver(contentPackageArchiver);
+            mavenArchiver.setOutputFile(finalFile);
+            mavenArchiver.createArchive(null, project, getMavenArchiveConfiguration(vaultProperties));
+
+            // set the file for the project's artifact and ensure the
+            // artifact is correctly handled with the "zip" handler
+            // (workaround for MNG-1682)
+            final Artifact projectArtifact = project.getArtifact();
+            projectArtifact.setFile(finalFile);
+            projectArtifact.setArtifactHandler(artifactHandlerManager.getArtifactHandler(PACKAGE_TYPE));
+
+        } catch (Exception e) {
+            throw new MojoExecutionException(e.toString(), e);
+        }
+    }
+
+    /**
+     * Computes the dependency string.
+     */
+    private void computeDependencies() throws IOException {
+        if (dependencies.length > 0) {
+            dependenciesString = PackageDependency.toString(Dependency.resolve(project, getLog(), dependencies));
+        }
+        Dependency.resolve(project, getLog(), repositoryStructurePackages);
+    }
+
+    /**
+     * Checks if the filter roots of this package are covered by the dependencies and also checks for colliding roots
+     * in the dependencies.
+     */
+    private void validateDependencies() throws MojoExecutionException {
+        List<String> errors = new DependencyValidator()
+                .addDependencies(dependencies)
+                .addDependencies(repositoryStructurePackages)
+                .setFilters(filters)
+                .validate()
+                .getErrors();
+
+        if (errors.size() > 0) {
+            String msg = String.format("%d error(s) detected during dependency analysis.", errors.size());
+            if (failOnDependencyErrors) {
+                getLog().error(msg);
+                for (String error: errors) {
+                    getLog().error(error);
+                }
+                throw new MojoExecutionException(msg);
+            }
+            getLog().warn(msg);
+            for (String error: errors) {
+                getLog().warn(error);
+            }
+        } else {
+            getLog().info("All dependencies satisfied.");
+        }
+    }
+
+    /**
+     * Computes the import-package definition from the given bundles if not provided by the project.
+     */
+    private void computeImportPackage() throws IOException {
+        TreeMap<String, Attrs> importParams = new TreeMap<String, Attrs>();
+        if (generatedImportPackage.exists()) {
+            String importPackageStr = FileUtils.fileRead(generatedImportPackage);
+            if (importPackageStr.length() > 0) {
+                importParams.putAll(new Parameters(importPackageStr));
+            }
+        }
+
+        // override computed patterns
+        if (importPackage != null) {
+            for (Map.Entry<String, Attrs> entry : new Parameters(importPackage).entrySet()) {
+                boolean delete = false;
+                String pkg = entry.getKey();
+                if ("-*".equals(pkg)) {
+                    importParams.clear();
+                    continue;
+                }
+                if (pkg.charAt(0) == '-') {
+                    pkg = pkg.substring(1);
+                    delete = true;
+                }
+                if (pkg.endsWith("*")) {
+                    String pkgDot = pkg.substring(0, pkg.length() - 1);
+                    if (!pkgDot.endsWith(".")) {
+                        // matches both, the packages and sub packages
+                        pkg = pkgDot;
+                        pkgDot = pkg + ".";
+                    }
+                    Iterator<Map.Entry<String, Attrs>> iter = importParams.entrySet().iterator();
+                    while (iter.hasNext()) {
+                        Map.Entry<String, Attrs> e = iter.next();
+                        String pkgName = e.getKey();
+                        if (pkgName.equals(pkg) || pkgName.startsWith(pkgDot)) {
+                            if (delete) {
+                                iter.remove();
+                            } else {
+                                e.setValue(entry.getValue());
+                            }
+                        }
+                    }
+                } else {
+                    if (delete) {
+                        importParams.remove(pkg);
+                    } else {
+                        importParams.put(pkg, entry.getValue());
+                    }
+                }
+            }
+        }
+
+        importPackage = Processor.printClauses(importParams);
+    }
+
+    private MavenArchiveConfiguration getMavenArchiveConfiguration(Properties vaultProperties) throws IOException {
+        if (archive == null) {
+            archive = new MavenArchiveConfiguration();
+            archive.setManifest(new ManifestConfiguration());
+
+            archive.setAddMavenDescriptor(true);
+            archive.setCompress(true);
+            archive.setIndex(false);
+            archive.getManifest().setAddDefaultSpecificationEntries(false);
+            archive.getManifest().setAddDefaultImplementationEntries(true);
+
+            PackageId id = new PackageId(group, name, version);
+            archive.addManifestEntry(MF_KEY_PACKAGE_TYPE, packageType.name().toLowerCase());
+            archive.addManifestEntry(MF_KEY_PACKAGE_ID, id.toString());
+            archive.addManifestEntry(MF_KEY_PACKAGE_DESC, vaultProperties.getProperty("description", ""));
+            if (dependenciesString != null && dependenciesString.length() > 0) {
+                archive.addManifestEntry(MF_KEY_PACKAGE_DEPENDENCIES, dependenciesString);
+            }
+            // be sure to avoid duplicates
+            Set<String> rts = new TreeSet<String>();
+            for (PathFilterSet p: filters.getFilterSets()) {
+                rts.add(p.getRoot());
+            }
+            String[] roots = rts.toArray(new String[rts.size()]);
+            Arrays.sort(roots);
+            archive.addManifestEntry(MF_KEY_PACKAGE_ROOTS, StringUtils.join(roots, ","));
+
+            if (StringUtils.isNotEmpty(importPackage)) {
+                archive.addManifestEntry(MF_KEY_IMPORT_PACKAGE, StringUtils.deleteWhitespace(importPackage));
+            }
+        }
+
+        return archive;
+    }
+
+    private Properties computeProperties() {
+        final Properties props = new Properties();
+
+        // find the description of the content package (bug #30546)
+        // this is allowed to be overwritten by the properties map (GRANITE-1527)
+        String description = project.getDescription();
+        if (description == null) {
+            description = project.getName();
+            if (description == null) {
+                description = project.getArtifactId();
+            }
+        }
+        props.put("description", description);
+
+        // add all user defined properties
+        // before the rest of the properties to prevent user
+        // overwriting of predefined properties
+        // (see JavaDoc of properties field for list)
+
+        // but make sure, that we don't have null values in there
+        for (Object o : properties.keySet()) {
+            if (properties.get(o) == null) {
+                properties.put(o, "");
+            }
+        }
+
+        props.putAll(properties);
+
+        // package descriptor properties
+        props.put("group", group);
+        props.put("name", name);
+        props.put("version", version);
+
+        // maven artifact identification
+        props.put("groupId", project.getGroupId());
+        props.put("artifactId", project.getArtifactId());
+
+        // dependencies
+        if (dependenciesString != null && dependenciesString.length() > 0) {
+            props.put("dependencies", dependenciesString);
+        }
+
+        // creation stamp
+        if (!props.containsKey("createdBy")) {
+            props.put("createdBy", System.getProperty("user.name"));
+        }
+        props.put("created", DATE_FORMAT.format(new Date()));
+
+        // configurable properties
+        props.put("requiresRoot", String.valueOf(requiresRoot));
+        props.put("allowIndexDefinitions", String.valueOf(allowIndexDefinitions));
+        props.put("packageType", packageType.name().toLowerCase());
+        return props;
+    }
+
+    private void copyFile(String source, File target) throws IOException {
+
+        // nothing to do if the file exists
+        if (target.exists()) {
+            return;
+        }
+
+        target.getParentFile().mkdirs();
+
+        InputStream ins = getClass().getResourceAsStream(source);
+        if (ins != null) {
+            OutputStream out = null;
+            try {
+                out = new FileOutputStream(target);
+                IOUtil.copy(ins, out);
+            } finally {
+                IOUtil.close(ins);
+                IOUtil.close(out);
+            }
+        }
+    }
+
+    private Map<String, File> copyEmbeddeds() throws IOException, MojoFailureException {
+        Map<String, File> fileMap = new HashMap<String, File>();
+        for (Embedded emb : embeddeds) {
+            final List<Artifact> artifacts = emb.getMatchingArtifacts(project);
+            if (artifacts.isEmpty()) {
+                if (failOnMissingEmbed) {
+                    throw new MojoFailureException("Embedded artifact specified " + emb + ", but no matching dependency artifact found. Add the missing dependency or fix the embed definition.");
+                } else {
+                    getLog().warn("No matching artifacts for " + emb);
+                    continue;
+                }
+            }
+            if (emb.getDestFileName() != null && artifacts.size() > 1) {
+                getLog().warn("destFileName defined but several artifacts match for " + emb);
+            }
+
+            String targetPath = emb.getTarget();
+            if (targetPath == null) {
+                targetPath = embeddedTarget;
+                if (targetPath == null) {
+                    final String loc = (prefix.length() == 0)
+                            ? "/apps/"
+                            : prefix;
+                    targetPath = loc + "bundles/install/";
+                    getLog().info("No target path set on " + emb + "; assuming default " + targetPath);
+                }
+            }
+            targetPath = makeAbsolutePath(targetPath);
+
+            targetPath = JCR_ROOT + targetPath;
+            targetPath = FileUtils.normalize(targetPath);
+
+            getLog().info("Embedding --- " + emb + " ---");
+            for (final Artifact artifact : artifacts) {
+                final File source = artifact.getFile();
+                String destFileName = emb.getDestFileName();
+
+                // todo: add support for patterns
+                if (destFileName == null) {
+                    destFileName = source.getName();
+                }
+                final String targetPathName = targetPath + destFileName;
+                final String targetNodePathName = targetPathName.substring(JCR_ROOT.length() - 1);
+
+                fileMap.put(targetPathName, source);
+                getLog().info(String.format("Embedding %s (from %s) -> %s", artifact.getId(), source.getAbsolutePath(), targetPathName));
+
+                if (emb.isFilter()) {
+                    addWorkspaceFilter(targetNodePathName);
+                }
+            }
+        }
+        return fileMap;
+    }
+
+    private Map<String, File> copySubPackages() throws IOException {
+        Map<String, File> fileMap = new HashMap<String, File>();
+        for (SubPackage pack : subPackages) {
+            final List<Artifact> artifacts = pack.getMatchingArtifacts(project);
+            if (artifacts.isEmpty()) {
+                getLog().warn("No matching artifacts for " + pack);
+                continue;
+            }
+
+            // get the package path
+            getLog().info("Embedding --- " + pack + " ---");
+            for (Artifact artifact : artifacts) {
+                final File source = artifact.getFile();
+
+                // load properties
+                ZipFile zip = null;
+                InputStream in = null;
+                Properties props = new Properties();
+                try {
+                    zip = new ZipFile(source, ZipFile.OPEN_READ);
+                    ZipEntry e = zip.getEntry("META-INF/vault/properties.xml");
+                    if (e == null) {
+                        getLog().error("Package does not contain properties.xml");
+                        throw new IOException("properties.xml missing");
+                    }
+                    in = zip.getInputStream(e);
+                    props.loadFromXML(in);
+                } finally {
+                    IOUtil.close(in);
+                    if (zip != null) {
+                        zip.close();
+                    }
+                }
+                PackageId pid = new PackageId(
+                        props.getProperty("group"),
+                        props.getProperty("name"),
+                        props.getProperty("version")
+                );
+                final String targetNodePathName = pid.getInstallationPath() + ".zip";
+                final String targetPathName = "jcr_root" + targetNodePathName;
+
+                fileMap.put(targetPathName, source);
+                getLog().info("Embedding " + artifact.getId() + " -> " + targetPathName);
+                if (pack.isFilter()) {
+                    addWorkspaceFilter(targetNodePathName);
+                }
+            }
+        }
+        return fileMap;
+    }
+
+    private void addWorkspaceFilter(final String filterRoot) {
+        filters.add(new PathFilterSet(filterRoot));
+    }
+
+    private String makeAbsolutePath(final String relPath) {
+        final String absPath;
+        if (!relPath.startsWith("/")) {
+            absPath = ((prefix.length() == 0) ? "/" : prefix) + relPath;
+            getLog().info("Relative path resolved to " + absPath);
+        } else {
+            absPath = relPath;
+        }
+
+        return absPath;
+    }
+
+    private void validatePackageType() throws MojoFailureException {
+        if (packageType == null) {
+            // auto detect...
+            boolean hasApps = false;
+            boolean hasOther = false;
+            for (PathFilterSet p: filters.getFilterSets()) {
+                String root = p.getRoot();
+                if ("/apps".equals(root) || root.startsWith("/apps/") || "/libs".equals(root) || root.startsWith("/libs/")) {
+                    hasApps = true;
+                } else {
+                    hasOther = true;
+                }
+            }
+            if (hasApps && !hasOther) {
+                packageType = PackageType.APPLICATION;
+            } else if (hasOther && !hasApps) {
+                packageType = PackageType.CONTENT;
+            } else {
+                packageType = PackageType.MIXED;
+            }
+        }
+    }
+
+}