jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From tri...@apache.org
Subject svn commit: r1512568 [20/39] - in /jackrabbit/commons/filevault/trunk: ./ parent/ vault-cli/ vault-cli/src/ vault-cli/src/main/ vault-cli/src/main/appassembler/ vault-cli/src/main/assembly/ vault-cli/src/main/java/ vault-cli/src/main/java/org/ vault-cl...
Date Sat, 10 Aug 2013 05:53:54 GMT
Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/InstallHookProcessor.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/InstallHookProcessor.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/InstallHookProcessor.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/InstallHookProcessor.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,255 @@
+/*
+ * 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.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.TreeMap;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.vault.fs.api.VaultInputSource;
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.packaging.InstallContext;
+import org.apache.jackrabbit.vault.packaging.InstallHook;
+import org.apache.jackrabbit.vault.packaging.PackageException;
+import org.apache.jackrabbit.vault.packaging.VaultPackage;
+import org.apache.jackrabbit.vault.util.Constants;
+import org.apache.jackrabbit.vault.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * processor for install hooks
+ */
+public class InstallHookProcessor {
+
+    /**
+     * default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(InstallHookProcessor.class);
+
+    private final TreeMap<String, Hook> hooks = new TreeMap<String, Hook>();
+
+    public void registerHooks(Archive archive, ClassLoader classLoader) throws PackageException {
+        try {
+            Archive.Entry root = archive.getRoot();
+            root = root.getChild(Constants.META_INF);
+            if (root == null) {
+                log.warn("Archive {} does not have a {} directory.", archive, Constants.META_INF);
+                return;
+            }
+            root = root.getChild(Constants.VAULT_DIR);
+            if (root == null) {
+                log.warn("Archive {} does not have a {} directory.", archive, Constants.VAULT_DIR);
+                return;
+            }
+            root = root.getChild(Constants.HOOKS_DIR);
+            if (root == null) {
+                log.debug("Archive {} does not have a {} directory.", archive, Constants.HOOKS_DIR);
+            } else {
+                for (Archive.Entry entry : root.getChildren()) {
+                    // only respect .jar files
+                    if (entry.getName().endsWith(".jar")) {
+                        registerHook(archive.getInputSource(entry), classLoader);
+                    }
+                }
+            }
+            
+            // also look for external hooks in properties
+            // currently only the format: "installhook.{name}.class" is supported
+            Properties props = archive.getMetaInf().getProperties();
+            if (props != null) {
+                Enumeration names = props.propertyNames();
+                while (names.hasMoreElements()) {
+                    String name = names.nextElement().toString();
+                    if (name.startsWith(VaultPackage.PREFIX_INSTALL_HOOK)) {
+                        String[] segs = Text.explode(name.substring(VaultPackage.PREFIX_INSTALL_HOOK.length()), '.');
+                        if (segs.length == 0 || segs.length > 2 || !segs[1].equals("class")) {
+                            throw new PackageException("Invalid installhook property: " + name);
+                        }
+                        Hook hook = new Hook(segs[0], props.getProperty(name), classLoader);
+                        initHook(hook);
+                    }
+                }
+            }
+        } catch (IOException e) {
+            throw new PackageException("I/O Error while registering hooks", e);
+        }
+    }
+
+    public void registerHook(VaultInputSource input, ClassLoader classLoader) throws IOException, PackageException {
+        // first we need to spool the jar file to disk.
+        File jarFile = File.createTempFile("vaulthook", ".jar");
+        Hook hook = new Hook(input.getSystemId(), jarFile, classLoader);
+
+        OutputStream out = null;
+        InputStream in = input.getByteStream();
+        try {
+            out = FileUtils.openOutputStream(jarFile);
+            IOUtils.copy(in, out);
+        } catch (IOException e) {
+            hook.destroy();
+            throw e;
+        } finally {
+            IOUtils.closeQuietly(in);
+            IOUtils.closeQuietly(out);
+        }
+        initHook(hook);
+    }
+
+    private void initHook(Hook hook) throws IOException, PackageException {
+        try {
+            hook.init();
+        } catch (IOException e) {
+            log.error("Error while initializing hook: {}", e.toString());
+            hook.destroy();
+            throw e;
+        } catch (PackageException e) {
+            log.error("Error while initializing hook: {}", e.toString());
+            hook.destroy();
+            throw e;
+        }
+        hooks.put(hook.name, hook);
+        log.info("Hook {} registered.", hook.name);
+    }
+
+    public boolean hasHooks() {
+        return !hooks.isEmpty();
+    }
+    
+    public boolean execute(InstallContextImpl context) {
+        for (Hook hook : hooks.values()) {
+            try {
+                hook.getHook().execute(context);
+            } catch (PackageException e) {
+                // abort processing only for prepare phase
+                if (context.getPhase() == InstallContext.Phase.PREPARE) {
+                    log.warn("Hook " + hook.name +" threw package exception. Prepare aborted.", e);
+                    context.setPhase(InstallContext.Phase.PREPARE_FAILED);
+                    execute(context);
+                    return false;
+                }
+                log.warn("Hook " + hook.name +" threw package exception. Ignored", e);
+            } catch (Throwable e) {
+                log.warn("Hook " + hook.name +" threw runtime exception.", e);
+            }
+            // if in end phase, shutdown hooks
+            if (context.getPhase() == InstallContext.Phase.END) {
+                hook.destroy();
+            }
+        }
+        return true;
+    }
+
+    private class Hook {
+
+        private final String name;
+
+        private final File jarFile;
+
+        private ClassLoader classLoader;
+
+        private ClassLoader parentClassLoader;
+
+        private InstallHook hook;
+
+        private String mainClassName;
+
+        private Hook(String name, String mainClassName, ClassLoader parentClassLoader) {
+            this.name = name;
+            this.mainClassName = mainClassName;
+            this.parentClassLoader = parentClassLoader;
+            this.jarFile = null;
+        }
+
+        private Hook(String name, File jarFile, ClassLoader parentClassLoader) {
+            this.name = name;
+            this.jarFile = jarFile;
+            this.parentClassLoader = parentClassLoader;
+        }
+
+        private void destroy() {
+            parentClassLoader = null;
+            classLoader = null;
+            hook = null;
+            if (jarFile != null) {
+                FileUtils.deleteQuietly(jarFile);
+            }
+        }
+
+        private void init() throws IOException, PackageException {
+            // create classloader
+            if (parentClassLoader == null) {
+                parentClassLoader = Thread.currentThread().getContextClassLoader();
+            }
+
+            if (jarFile != null) {
+                // open jar file and get manifest
+                JarFile jar = new JarFile(jarFile);
+                Manifest mf = jar.getManifest();
+                if (mf == null) {
+                    throw new PackageException("hook jar file does not have a manifest: " + name);
+                }
+                mainClassName = mf.getMainAttributes().getValue("Main-Class");
+                if (mainClassName == null) {
+                    throw new PackageException("hook manifest file does not have a Main-Class entry: " + name);
+                }
+                classLoader = URLClassLoader.newInstance(
+                    new URL[]{jarFile.toURL()},
+                    parentClassLoader);
+            } else {
+                classLoader = parentClassLoader;
+            }
+            loadMainClass();
+        }
+
+        private void loadMainClass() throws PackageException {
+            log.info("Loading Hook {}: Main-Class = {}", name, mainClassName);
+
+            // find main class
+            Class clazz;
+            try {
+                clazz = classLoader.loadClass(mainClassName);
+            } catch (ClassNotFoundException e) {
+                throw new PackageException("hook's main class " + mainClassName + " not found: " + name, e);
+            }
+            if (!InstallHook.class.isAssignableFrom(clazz)) {
+                throw new PackageException("hook's main class " + mainClassName + " does not implement the InstallHook interface: " + name);
+            }
+            // create instance
+            try {
+                hook = (InstallHook) clazz.newInstance();
+            } catch (Exception e) {
+                throw new PackageException("hook's main class " + mainClassName + " could not be instantiated.", e);
+            }
+        }
+
+        public InstallHook getHook() {
+            return hook;
+        }
+    }
+}

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageDefinitionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageDefinitionImpl.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageDefinitionImpl.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageDefinitionImpl.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,947 @@
+/*
+ * 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.impl;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Calendar;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.util.ISO8601;
+import org.apache.jackrabbit.vault.fs.Mounter;
+import org.apache.jackrabbit.vault.fs.api.PathFilterSet;
+import org.apache.jackrabbit.vault.fs.api.ProgressTrackerListener;
+import org.apache.jackrabbit.vault.fs.api.RepositoryAddress;
+import org.apache.jackrabbit.vault.fs.api.VaultFileSystem;
+import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
+import org.apache.jackrabbit.vault.fs.config.DefaultMetaInf;
+import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
+import org.apache.jackrabbit.vault.fs.config.MetaInf;
+import org.apache.jackrabbit.vault.fs.io.AbstractExporter;
+import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.fs.io.Importer;
+import org.apache.jackrabbit.vault.packaging.Dependency;
+import org.apache.jackrabbit.vault.packaging.ExportPostProcessor;
+import org.apache.jackrabbit.vault.packaging.JcrPackageDefinition;
+import org.apache.jackrabbit.vault.packaging.PackageId;
+import org.apache.jackrabbit.vault.packaging.VaultPackage;
+import org.apache.jackrabbit.vault.util.Constants;
+import org.apache.jackrabbit.vault.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The JCR package definition is used to operate with a unwrapped package
+ * in the repository.
+ */
+public class JcrPackageDefinitionImpl implements JcrPackageDefinition {
+
+    /**
+     * default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(JcrPackageDefinitionImpl.class);
+
+    /**
+     * underlying node
+     */
+    private Node defNode;
+
+    private String userId;
+
+    /**
+     * Creates a new definition base on the underlying node.
+     * @param definitionNode the definition node
+     */
+    public JcrPackageDefinitionImpl(Node definitionNode) {
+        this.defNode = definitionNode;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Node getNode() {
+        return defNode;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public PackageId getId() {
+        String group = get(PN_GROUP);
+        String name = get(PN_NAME);
+        String version = get(PN_VERSION);
+        if (group == null || name == null || name.length() == 0) {
+            // backward compatible
+            String path = getInstallationPath();
+            if (path == null) {
+                log.warn("Unable to calculate installation path. setting to 'unknown'");
+                path = "unknown";
+            }
+            return new PackageId(path, version);
+        } else {
+            return new PackageId(group, name, version);
+        }
+    }
+
+    /**
+     * Writes the properties derived from the package id to the content
+     * @param id the package id
+     * @param autoSave if <code>true</code> the changes are saved automatically.
+     */
+    public void setId(PackageId id, boolean autoSave) {
+        set(PN_GROUP, id.getGroup(), false);
+        set(PN_NAME, id.getName(), false);
+        set(PN_VERSION, id.getVersionString(), false);
+    }
+
+    /**
+     * Returns the installation path. If the path is not defined in the definition,
+     * the grand-parent of the underlying node is returned. if the path would end
+     * with .zip or .jar, the extension is truncated.
+     * @return the installation path or <code>null</code> if it cannot be determined.
+     */
+    private String getInstallationPath() {
+        try {
+            String path = get("path");
+            if (path == null || path.length() == 0) {
+                // get grand parent
+                path = defNode.getParent().getParent().getPath();
+            }
+            int idx = path.lastIndexOf('.');
+            if (idx > 0) {
+                String ext = path.substring(idx);
+                if (ext.equalsIgnoreCase(".zip") || ext.equalsIgnoreCase(".jar")) {
+                    path = path.substring(0, idx);
+                }
+            }
+            return path;
+        } catch (RepositoryException e) {
+            log.warn("Error during getInstallationPath()", e);
+            return null;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isUnwrapped() {
+        try {
+            // backward compat check
+            return defNode.hasProperty("unwrapped")
+                    || defNode.hasProperty(PN_LAST_UNWRAPPED);
+        } catch (RepositoryException e) {
+            log.warn("Error during isUnwrapped()", e);
+            return false;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isModified() {
+        if (!isUnwrapped()) {
+            return false;
+        }
+        Calendar mod = getLastModified();
+        if (mod == null) {
+            return false;
+        }
+        Calendar uw = getLastWrapped();
+        if (uw == null) {
+            uw = getLastUnwrapped();
+        }
+        if (uw == null) {
+            // backward compat check
+            try {
+                if (defNode.hasProperty("unwrapped")) {
+                    return true;
+                }
+            } catch (RepositoryException e) {
+                log.warn("Error while checking unwrapped property", e);
+            }
+            return false;
+        }
+        return mod.after(uw);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void unwrap(VaultPackage pack, boolean force)
+            throws RepositoryException, IOException {
+        unwrap(pack, force, true);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void unwrap(VaultPackage pack, boolean force, boolean autoSave)
+            throws RepositoryException, IOException {
+        if (!force && isUnwrapped()) {
+            return;
+        }
+        log.info("unwrapping package {}", pack == null ? "(unknown)" : pack.getId());
+        long now = System.currentTimeMillis();
+        if (pack != null && pack.getFile() != null) {
+            MetaInf inf = pack.getMetaInf();
+            // explode definition if present
+            if (inf.hasDefinition()) {
+                extractDefinition(pack.getArchive(), false);
+            }
+            if (inf.getFilter() != null) {
+                writeFilter(inf.getFilter(), false);
+            }
+            if (inf.getProperties() != null) {
+                writeProperties(inf.getProperties(), false);
+            }
+        }
+        defNode.setProperty("unwrapped", (Value) null);
+        defNode.setProperty(PN_LAST_UNWRAPPED, Calendar.getInstance());
+        if (autoSave) {
+            defNode.save();
+        }
+        if (log.isDebugEnabled()) {
+            log.debug("unwrapping package {} completed in {}ms", getId(), System.currentTimeMillis() - now);
+        }
+    }
+
+    /**
+     * Extracts the content represenatation of a definition store the given
+     * package to this node.
+     *
+     * @param packArchive the archive of the package
+     * @param autoSave saves changed automatically if <code>true</code>
+     * @throws RepositoryException if an error occurs
+     */
+    private void extractDefinition(Archive packArchive, boolean autoSave)
+            throws RepositoryException {
+        Archive archive = null;
+        try {
+            archive = packArchive.getSubArchive("META-INF/vault/definition", true);
+        } catch (IOException e) {
+            log.error("Error while accessing sub archive", e);
+        }
+        if (archive == null) {
+            log.warn("Unable to extract definition. No such entry in archive.");
+            return;
+        }
+        // need to 'save' last unpacked props
+        Value lastUnpacked = null;
+        if (defNode.hasProperty(PN_LAST_UNPACKED)) {
+            lastUnpacked = defNode.getProperty(PN_LAST_UNPACKED).getValue();
+        }
+        Value lastUnpackedBy = null;
+        if (defNode.hasProperty(PN_LAST_UNPACKED_BY)) {
+            lastUnpackedBy = defNode.getProperty(PN_LAST_UNPACKED_BY).getValue();
+        }
+
+        Session session = defNode.getSession();
+        String rootPath = defNode.getPath();
+        DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+        filter.add(new PathFilterSet(rootPath));
+
+        try {
+            Node rootNode = session.getNode(rootPath);
+            String defPath = defNode.getPath().substring(1);
+
+            Importer importer = new Importer();
+            // disable saving
+            importer.getOptions().setAutoSaveThreshold(Integer.MAX_VALUE);
+            importer.getOptions().setFilter(filter);
+            importer.run(archive, rootNode);
+
+            // refresh defNode if it was replaced during unwrap
+            defNode = session.getRootNode().getNode(defPath);
+
+            // set props again
+            if (lastUnpacked != null) {
+                defNode.setProperty(PN_LAST_UNPACKED, lastUnpacked);
+            }
+            if (lastUnpackedBy != null) {
+                defNode.setProperty(PN_LAST_UNPACKED_BY, lastUnpackedBy);
+            }
+            if (autoSave) {
+                defNode.save();
+            }
+        } catch (Exception e) {
+            log.error("Unable to extract definition: {}", e.toString());
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Dependency[] getDependencies() {
+        try {
+            if (defNode.hasProperty(PN_DEPENDENCIES)) {
+                Property p = defNode.getProperty(PN_DEPENDENCIES);
+                List<Dependency> deps = new LinkedList<Dependency>();
+                if (p.getDefinition().isMultiple()) {
+                    for (Value v: p.getValues()) {
+                        deps.add(Dependency.fromString(v.getString()));
+                    }
+                } else {
+                    deps.add(Dependency.fromString(p.getString()));
+                }
+                return deps.toArray(new Dependency[deps.size()]);
+            }
+        } catch (RepositoryException e) {
+            log.error("Error during getDependencies()", e);
+        }
+        return Dependency.EMPTY;
+    }
+
+    /**
+     * Load the given properties from the content
+     * @param props the properties to load
+     */
+    protected void loadProperties(Properties props) {
+        PackageId id = getId();
+        setProperty(props, VaultPackage.NAME_VERSION, id.getVersionString());
+        setProperty(props, VaultPackage.NAME_NAME, id.getName());
+        setProperty(props, VaultPackage.NAME_GROUP, id.getGroup());
+        setProperty(props, VaultPackage.NAME_BUILD_COUNT, get(PN_BUILD_COUNT));
+        setProperty(props, VaultPackage.NAME_DESCRIPTION, get(PN_DESCRIPTION));
+        setProperty(props, VaultPackage.NAME_REQUIRES_ROOT, get(PN_REQUIRES_ROOT));
+        setProperty(props, VaultPackage.NAME_REQUIRES_RESTART, get(PN_REQUIRES_RESTART));
+        setProperty(props, VaultPackage.NAME_LAST_MODIFIED, getCalendar(PN_LASTMODIFIED));
+        setProperty(props, VaultPackage.NAME_LAST_MODIFIED_BY, get(PN_LASTMODIFIED_BY));
+        setProperty(props, VaultPackage.NAME_LAST_WRAPPED, getCalendar(PN_LAST_WRAPPED));
+        setProperty(props, VaultPackage.NAME_LAST_WRAPPED_BY, get(PN_LAST_WRAPPED_BY));
+        setProperty(props, VaultPackage.NAME_CREATED, getCalendar(PN_CREATED));
+        setProperty(props, VaultPackage.NAME_CREATED_BY, get(PN_CREATED_BY));
+        setProperty(props, VaultPackage.NAME_DEPENDENCIES, Dependency.toString(getDependencies()));
+        setProperty(props, VaultPackage.NAME_AC_HANDLING, get(PN_AC_HANDLING));
+        setProperty(props, VaultPackage.NAME_CND_PATTERN, get(PN_CND_PATTERN));
+    }
+
+    /**
+     * internal method that adds or removes a property
+     * @param props the properties
+     * @param name the name of the properties
+     * @param value the value
+     */
+    private static void setProperty(Properties props, String name, String value) {
+        if (value == null) {
+            props.remove(name);
+        } else {
+            props.put(name, value);
+        }
+    }
+
+    /**
+     * internal method that adds or removes a property
+     * @param props the properties
+     * @param name the name of the properties
+     * @param value the value
+     */
+    private static void setProperty(Properties props, String name, Calendar value) {
+        if (value == null) {
+            props.remove(name);
+        } else {
+            props.put(name, ISO8601.format(value));
+        }
+    }
+
+    /**
+     * Writes the given properties to the content.
+     * @param props the properties
+     * @param autoSave saves the changes automatically if <code>true</code>
+     */
+    protected void writeProperties(Properties props, boolean autoSave) {
+        try {
+            // sanitize lastModBy property due to former bug that used the
+            // lastMod value
+            if (props.getProperty(VaultPackage.NAME_LAST_MODIFIED) != null
+                    && props.getProperty(VaultPackage.NAME_LAST_MODIFIED).equals(props.getProperty(VaultPackage.NAME_LAST_MODIFIED_BY))) {
+                props = new Properties(props);
+                props.setProperty(VaultPackage.NAME_LAST_MODIFIED_BY, "unknown");
+            }
+
+            // Note that the 'path', 'group' and 'name' properties are usually
+            // directly linked to the location of the package in the content.
+            // however, if a definition is unwrapped at another location, eg
+            // in package-share, it is convenient if the original properties
+            // are available in the content.
+            defNode.setProperty(PN_VERSION, props.getProperty(VaultPackage.NAME_VERSION));
+            defNode.setProperty(PN_BUILD_COUNT, props.getProperty(VaultPackage.NAME_BUILD_COUNT));
+            defNode.setProperty(PN_NAME, props.getProperty(VaultPackage.NAME_NAME));
+            defNode.setProperty(PN_GROUP, props.getProperty(VaultPackage.NAME_GROUP));
+            String deps = props.getProperty(VaultPackage.NAME_DEPENDENCIES);
+            if (defNode.hasProperty(PN_DEPENDENCIES)) {
+                defNode.getProperty(PN_DEPENDENCIES).remove();
+            }
+            if (deps != null) {
+                Dependency[] d = Dependency.parse(deps);
+                String[] ds = new String[d.length];
+                for (int i=0; i<ds.length; i++) {
+                    ds[i] = d[i].toString();
+                }
+                defNode.setProperty(PN_DEPENDENCIES, ds);
+            }
+            defNode.setProperty(PN_DESCRIPTION, props.getProperty(VaultPackage.NAME_DESCRIPTION));
+            defNode.setProperty(PN_REQUIRES_ROOT, Boolean.valueOf(props.getProperty(VaultPackage.NAME_REQUIRES_ROOT, "false")));
+            defNode.setProperty(PN_REQUIRES_RESTART, Boolean.valueOf(props.getProperty(VaultPackage.NAME_REQUIRES_RESTART, "false")));
+            defNode.setProperty(PN_LASTMODIFIED, getDate(props.getProperty(VaultPackage.NAME_LAST_MODIFIED)));
+            defNode.setProperty(PN_LASTMODIFIED_BY, props.getProperty(VaultPackage.NAME_LAST_MODIFIED_BY));
+            defNode.setProperty(PN_CREATED, getDate(props.getProperty(VaultPackage.NAME_CREATED)));
+            defNode.setProperty(PN_CREATED_BY, props.getProperty(VaultPackage.NAME_CREATED_BY));
+            defNode.setProperty(PN_LAST_WRAPPED, getDate(props.getProperty(VaultPackage.NAME_LAST_WRAPPED)));
+            defNode.setProperty(PN_LAST_WRAPPED_BY, props.getProperty(VaultPackage.NAME_LAST_WRAPPED_BY));
+            defNode.setProperty(PN_AC_HANDLING, props.getProperty(VaultPackage.NAME_AC_HANDLING));
+            defNode.setProperty(PN_CND_PATTERN, props.getProperty(VaultPackage.NAME_CND_PATTERN));
+            defNode.setProperty(PN_DISABLE_INTERMEDIATE_SAVE, props.getProperty(VaultPackage.NAME_DISABLE_INTERMEDIATE_SAVE));
+            if (autoSave) {
+                defNode.save();
+            }
+        } catch (RepositoryException e) {
+            log.error("error while saving properties.", e);
+        }
+    }
+
+    /**
+     * Internal method that converts a ISO date to a calendar.
+     * @param iso the iso8601 formatted date
+     * @return the calendar or <code>null</code>
+     */
+    private static Calendar getDate(String iso) {
+        if (iso == null) {
+            return null;
+        }
+        // check for missing : in timezone part
+        String tzd = iso.substring(iso.length() - 4);
+        if (tzd.indexOf(':') < 0) {
+            iso = iso.substring(0, iso.length() - 4);
+            iso += tzd.substring(0, 2);
+            iso += ":";
+            iso += tzd.substring(2);
+        }
+        return ISO8601.parse(iso);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void dumpCoverage(ProgressTrackerListener listener)
+            throws RepositoryException {
+        WorkspaceFilter filter = getMetaInf().getFilter();
+        filter.dumpCoverage(defNode.getSession(), listener, false);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String get(String name) {
+        try {
+            if (defNode.hasProperty(name)) {
+                return defNode.getProperty(name).getString();
+            }
+        } catch (RepositoryException e) {
+            log.error("Error during get({})", name, e);
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean getBoolean(String name) {
+        try {
+            if (defNode.hasProperty(name)) {
+                return defNode.getProperty(name).getBoolean();
+            }
+        } catch (RepositoryException e) {
+            log.error("Error during getBoolean({})", name, e);
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Calendar getCalendar(String name) {
+        try {
+            if (defNode.hasProperty(name)) {
+                return defNode.getProperty(name).getDate();
+            }
+        } catch (RepositoryException e) {
+            log.error("Error during getCalendar({})", name, e);
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void set(String name, String value, boolean autoSave) {
+        try {
+            defNode.setProperty(name, value);
+            touch(null, autoSave);
+        } catch (RepositoryException e) {
+            log.error("Error during set({})", name, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void set(String name, Calendar value, boolean autoSave) {
+        try {
+            defNode.setProperty(name, value);
+            touch(null, autoSave);
+        } catch (RepositoryException e) {
+            log.error("Error during set({})", name, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void set(String name, boolean value, boolean autoSave) {
+        try {
+            defNode.setProperty(name, value);
+            touch(null, autoSave);
+        } catch (RepositoryException e) {
+            log.error("Error during set({})", name, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void touch(Calendar now, boolean autoSave) {
+        try {
+            defNode.setProperty(PN_LASTMODIFIED,
+                    now == null ? Calendar.getInstance() : now);
+            defNode.setProperty(PN_LASTMODIFIED_BY, getUserId());
+            if (autoSave) {
+                defNode.save();
+            }
+        } catch (RepositoryException e) {
+            log.error("Error during touch()", e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setFilter(WorkspaceFilter filter, boolean autoSave) {
+        try {
+            JcrWorkspaceFilter.saveFilter(filter, defNode, autoSave);
+        } catch (RepositoryException e) {
+            log.error("Error while saving filter.", e);
+        }
+    }
+
+    /**
+     * Seals the package for assembly:
+     * - touches this package
+     * - increments the build count
+     * - updates the created(by) properties
+     * - updates the lastUnwrapped(by) properties
+     * - clears the unwrapped property
+     *
+     * @param now the date or <code>null</code>
+     * @param autoSave saves the changes automatically if <code>true</code>
+     */
+    public void sealForAssembly(Calendar now, boolean autoSave) {
+        try {
+            if (now == null) {
+                now = Calendar.getInstance();
+            }
+            set(PN_BUILD_COUNT, String.valueOf(getBuildCount() + 1), autoSave);
+            defNode.setProperty(PN_CREATED, now);
+            defNode.setProperty(PN_CREATED_BY, getUserId());
+            defNode.setProperty(PN_LAST_WRAPPED, now);
+            defNode.setProperty(PN_LAST_WRAPPED_BY, getUserId());
+            defNode.setProperty(PN_LAST_UNWRAPPED, now);
+            defNode.setProperty(PN_LAST_UNWRAPPED_BY, getUserId());
+            defNode.setProperty("unwrapped", (Value) null);
+            touch(now, autoSave);
+        } catch (RepositoryException e) {
+            log.error("Error during sealForAssembly()", e);
+        }
+    }
+
+    /**
+     * Seals the package for assembly:
+     * - touches this package
+     * - increments the build count
+     * - updates the lastUnwrapped(by) properties
+     * - clears the unwrapped property
+     *
+     * @param now the date or <code>null</code>
+     * @param autoSave saves the changes automatically if <code>true</code>
+     */
+    public void sealForRewrap(Calendar now, boolean autoSave) {
+        try {
+            if (now == null) {
+                now = Calendar.getInstance();
+            }
+            defNode.setProperty(PN_BUILD_COUNT, String.valueOf(getBuildCount() + 1));
+            if (!defNode.hasProperty(PN_CREATED)) {
+                defNode.setProperty(PN_CREATED, now);
+                defNode.setProperty(PN_CREATED_BY, getUserId());
+            }
+            defNode.setProperty(PN_LAST_WRAPPED, now);
+            defNode.setProperty(PN_LAST_WRAPPED_BY, getUserId());
+            defNode.setProperty(PN_LAST_UNWRAPPED, now);
+            defNode.setProperty(PN_LAST_UNWRAPPED_BY, getUserId());
+            defNode.setProperty("unwrapped", (Value) null);
+            touch(now, false);
+            if (autoSave) {
+                defNode.save();
+            }
+        } catch (RepositoryException e) {
+            log.error("Error during sealForRewrap()", e);
+        }
+    }
+
+    /**
+     * Touches the lastUnpacked (i.e. installed) properties.
+     * @param now the date or <code>null</code>
+     * @param autoSave saves the changes automatically if <code>true</code>
+     */
+    public void touchLastUnpacked(Calendar now, boolean autoSave) {
+        try {
+            defNode.setProperty(PN_LAST_UNPACKED,
+                    now == null ? Calendar.getInstance() : now);
+            defNode.setProperty(PN_LAST_UNPACKED_BY, getUserId());
+            if (autoSave) {
+                defNode.save();
+            }
+        } catch (RepositoryException e) {
+            log.error("Error during touchLastUnpacked()", e);
+        }
+    }
+
+    /**
+     * Clears the last unpacked properties.
+     * @param autoSave saves the changes automatically if <code>true</code>
+     */
+    public void clearLastUnpacked(boolean autoSave) {
+        try {
+            if (defNode.hasProperty(PN_LAST_UNPACKED)) {
+                defNode.getProperty(PN_LAST_UNPACKED).remove();
+            }
+            if (defNode.hasProperty(PN_LAST_UNPACKED_BY)) {
+                defNode.getProperty(PN_LAST_UNPACKED_BY).remove();
+            }
+            if (autoSave) {
+                defNode.save();
+            }
+        } catch (RepositoryException e) {
+            log.error("Error during clearLastUnpacked()", e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Calendar getLastModified() {
+        return getCalendar(PN_LASTMODIFIED);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getLastModifiedBy() {
+        return get(PN_LASTMODIFIED_BY);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Calendar getCreated() {
+        return getCalendar(PN_CREATED);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getCreatedBy() {
+        return get(PN_CREATED_BY);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Calendar getLastUnwrapped() {
+        return getCalendar(PN_LAST_UNWRAPPED);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getLastWrappedBy() {
+        return get(PN_LAST_WRAPPED_BY);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Calendar getLastWrapped() {
+        return getCalendar(PN_LAST_WRAPPED);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getLastUnwrappedBy() {
+        return get(PN_LAST_UNWRAPPED_BY);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Calendar getLastUnpacked() {
+        return getCalendar(PN_LAST_UNPACKED);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getLastUnpackedBy() {
+        return get(PN_LAST_UNPACKED_BY);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Deprecated
+    public boolean requiresRoot() {
+        return getBoolean(PN_REQUIRES_ROOT);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean requiresRestart() {
+        return getBoolean(PN_REQUIRES_RESTART);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AccessControlHandling getAccessControlHandling() {
+        String acHandling = get(PN_AC_HANDLING);
+        try {
+            return acHandling == null
+                    ? null
+                    : AccessControlHandling.valueOf(acHandling.toUpperCase());
+        } catch (IllegalArgumentException e) {
+            log.warn("invalid access control handling in definition: {} of {}", acHandling, getId());
+            return null;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getDescription() {
+        return get(PN_DESCRIPTION);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getBuildCount() {
+        try {
+            String bc = get(PN_BUILD_COUNT);
+            return bc == null ? 0 : Long.valueOf(bc);
+        } catch (NumberFormatException e) {
+            log.warn("Wrong build count in {}.", getId(), e);
+            return 0;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public MetaInf getMetaInf() throws RepositoryException {
+        DefaultMetaInf inf = new DefaultMetaInf();
+        inf.setFilter(readFilter());
+
+        // add properties
+        Properties props = new Properties();
+        loadProperties(props);
+        inf.setProperties(props);
+
+        return inf;
+    }
+
+    /**
+     * Loads the workspace filter from the definition
+     * @return the workspace filter
+     * @throws RepositoryException if an error occurs
+     */
+    public WorkspaceFilter readFilter() throws RepositoryException {
+        return JcrWorkspaceFilter.loadFilter(defNode);
+    }
+
+    /**
+     * Writes the workspace filter to the definition
+     * @param filter the filter
+     * @param save automatically save the changes if <code>true</code>
+     * @throws RepositoryException if an error occurs
+     */
+    public void writeFilter(WorkspaceFilter filter, boolean save)
+            throws RepositoryException {
+        JcrWorkspaceFilter.saveFilter(filter, defNode, save);
+    }
+
+    /**
+     * Returns a export processor that add the inlines definition package to
+     * the exporter.
+     * @return the export processor for this definition
+     */
+    public ExportPostProcessor getInjectProcessor() {
+        return new InjectProcessor(defNode);
+    }
+
+    /**
+     * Returns the user id of the current session. if the userid provided by
+     * the session is <code>null</code>, "system" is returned.
+     * @return the user id
+     */
+    private String getUserId() {
+        if (userId == null) {
+            try {
+                if (defNode != null) {
+                    userId = defNode.getSession().getUserID();
+                }
+            } catch (RepositoryException e) {
+                // ignore
+            }
+            if (userId == null) {
+                userId = "system";
+            }
+        }
+        return userId;
+    }
+
+    /**
+     * Returns a new state object that can be used to save modification information.
+     * @return a new state object.
+     */
+    protected State getState() {
+        return new State().load(this);
+    }
+
+    /**
+     * Sets the information stored in the state object back to this definition.
+     * @param state the sate
+     */
+    protected void setState(State state) {
+        state.save(this);
+    }
+
+    protected static class State {
+
+        private final String[] PROPERTY_NAMES = {
+                PN_LAST_UNPACKED,    PN_LAST_UNWRAPPED,    PN_LASTMODIFIED,    PN_LAST_WRAPPED,
+                PN_LAST_UNPACKED_BY, PN_LAST_UNWRAPPED_BY, PN_LASTMODIFIED_BY, PN_LAST_WRAPPED_BY
+        };
+        private final Value[] values = new Value[PROPERTY_NAMES.length];
+
+        private State load(JcrPackageDefinitionImpl def) {
+            for (int i=0; i<PROPERTY_NAMES.length; i++) {
+                try {
+                    if (def.defNode.hasProperty(PROPERTY_NAMES[i])) {
+                        values[i] = def.defNode.getProperty(PROPERTY_NAMES[i]).getValue();
+                    } else {
+                        values[i] = null;
+                    }
+                } catch (RepositoryException e) {
+                    log.error("Error while reading property {}: {}", PROPERTY_NAMES[i], e.toString());
+                }
+            }
+            return this;
+        }
+
+        private State save(JcrPackageDefinitionImpl def) {
+            for (int i=0; i<PROPERTY_NAMES.length; i++) {
+                if (values[i] != null) {
+                    try {
+                        def.defNode.setProperty(PROPERTY_NAMES[i], values[i]);
+                    } catch (RepositoryException e) {
+                        log.error("Error while setting {}: {}", PROPERTY_NAMES[i], e.toString());
+                    }
+                }
+            }
+            return this;
+        }
+    }
+
+    private static class InjectProcessor implements ExportPostProcessor {
+
+        private final Node defNode;
+
+        private InjectProcessor(Node defNode) {
+            this.defNode = defNode;
+        }
+
+        public void process(AbstractExporter exporter) {
+            try {
+                // remove temporarily the 'unpacked' properties.
+                // todo: do differently as soon we can filter properties
+                if (defNode.hasProperty(PN_LAST_UNPACKED)) {
+                    defNode.getProperty(PN_LAST_UNPACKED).remove();
+                }
+                if (defNode.hasProperty(PN_LAST_UNPACKED_BY)) {
+                    defNode.getProperty(PN_LAST_UNPACKED_BY).remove();
+                }
+
+                String rootPath = defNode.getPath();
+                DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
+                filter.add(new PathFilterSet(rootPath));
+                RepositoryAddress addr;
+                try {
+                    addr = new RepositoryAddress(
+                            Text.escapePath("/" + defNode.getSession().getWorkspace().getName() + rootPath));
+                } catch (URISyntaxException e) {
+                    throw new IllegalArgumentException(e);
+                }
+                VaultFileSystem jcrfs = Mounter.mount(null, filter, addr, "/definition", defNode.getSession());
+                exporter.setRelativePaths(true);
+                exporter.setRootPath("");
+                exporter.createDirectory(Constants.META_DIR + "/definition");
+                exporter.export(jcrfs.getRoot(), Constants.META_DIR + "/definition");
+                jcrfs.unmount();
+            } catch (Exception e) {
+                log.error("Error during post processing", e);
+            } finally {
+                try {
+                    // revert removed properties
+                    defNode.refresh(false);
+                } catch (RepositoryException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageImpl.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageImpl.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageImpl.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,611 @@
+/*
+ * 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.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.vault.fs.api.ImportMode;
+import org.apache.jackrabbit.vault.fs.api.ProgressTrackerListener;
+import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
+import org.apache.jackrabbit.vault.packaging.CyclicDependencyException;
+import org.apache.jackrabbit.vault.packaging.DependencyUtil;
+import org.apache.jackrabbit.vault.packaging.ExportOptions;
+import org.apache.jackrabbit.vault.packaging.JcrPackage;
+import org.apache.jackrabbit.vault.packaging.JcrPackageDefinition;
+import org.apache.jackrabbit.vault.packaging.PackageException;
+import org.apache.jackrabbit.vault.packaging.PackageId;
+import org.apache.jackrabbit.vault.packaging.VaultPackage;
+import org.apache.jackrabbit.vault.util.JcrConstants;
+import org.apache.jackrabbit.vault.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implements a JcrPackage
+ */
+public class JcrPackageImpl implements JcrPackage {
+
+    /**
+     * default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(JcrPackageImpl.class);
+
+    /**
+     * underlying node
+     */
+    private Node node;
+
+    /**
+     * underlying package
+     */
+    private ZipVaultPackage pack;
+
+    /**
+     * underlying definition
+     */
+    private JcrPackageDefinitionImpl def;
+
+    public JcrPackageImpl(Node node) throws RepositoryException {
+        this.node = node;
+    }
+
+    protected JcrPackageImpl(Node node, ZipVaultPackage pack) throws RepositoryException {
+        this.node = node;
+        this.pack = pack;
+    }
+
+    protected JcrPackageImpl(Node node, ZipVaultPackage pack, JcrPackageDefinitionImpl def)
+            throws RepositoryException {
+        this.node = node;
+        this.pack = pack;
+        this.def = def;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JcrPackageDefinition getDefinition() throws RepositoryException {
+        if (def == null) {
+            if (isValid()) {
+                Node defNode = getDefNode();
+                def = defNode == null
+                        ? null
+                        : new JcrPackageDefinitionImpl(defNode);
+            }
+        }
+        return def;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int compareTo(JcrPackage o) {
+        try {
+            JcrPackageDefinition d1 = getDefinition();
+            JcrPackageDefinition d2 = o.getDefinition();
+            return d1.getId().compareTo(d2.getId());
+        } catch (Exception e) {
+            log.error("error during compare: {}", e.toString());
+            return 0;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isValid() {
+        try {
+            if (node != null) {
+                if (node.isNodeType(JcrConstants.NT_HIERARCHYNODE) && node.hasNode(JcrConstants.JCR_CONTENT)) {
+                    if (node.getNode(JcrConstants.JCR_CONTENT).isNodeType(NT_VLT_PACKAGE)) {
+                        return true;
+                    }
+                }
+            }
+        } catch (RepositoryException e) {
+            log.warn("Error during evaluation of isValid()", e);
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isInstalled() throws RepositoryException {
+        JcrPackageDefinition def = getDefinition();
+        return def != null && def.getLastUnpacked() != null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Node getNode() {
+        return node;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isSealed() {
+        try {
+            if (!isValid()) {
+                return false;
+            }
+            if (getSize() == 0) {
+                return false;
+            }
+            if (getDefinition() == null) {
+                return true;
+            }
+            return !def.isModified();
+        } catch (RepositoryException e) {
+            log.warn("Error during isSealed()", e);
+            return false;
+        }
+
+    }
+
+    /**
+     * Creates a new jcr vault package.
+     *
+     * @param parent the parent node
+     * @param pid the package id of the new package.
+     * @param pack the underlying zip package or null.
+     * @param autoSave if <code>true</code> the changes are persisted immediately
+     * @return the created jcr vault package.
+     * @throws RepositoryException if an repository error occurs
+     * @throws IOException if an I/O error occurs
+     *
+     * @since 2.3.0
+     */
+    public static JcrPackage createNew(Node parent, PackageId pid, VaultPackage pack, boolean autoSave)
+            throws RepositoryException, IOException {
+        Node node = parent.addNode(Text.getName(pid.getInstallationPath() + ".zip"), JcrConstants.NT_FILE);
+        Node content = node.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE);
+        content.addMixin(NT_VLT_PACKAGE);
+        Node defNode = content.addNode(NN_VLT_DEFINITION);
+        JcrPackageDefinition def = new JcrPackageDefinitionImpl(defNode);
+        def.set(JcrPackageDefinition.PN_NAME, pid.getName(), false);
+        def.set(JcrPackageDefinition.PN_GROUP, pid.getGroup(), false);
+        def.set(JcrPackageDefinition.PN_VERSION, pid.getVersionString(), false);
+        def.touch(null, false);
+        content.setProperty(JcrConstants.JCR_LASTMODIFIED, Calendar.getInstance());
+        content.setProperty(JcrConstants.JCR_MIMETYPE, MIME_TYPE);
+        InputStream in = new ByteArrayInputStream(new byte[0]);
+        try {
+            if (pack != null && pack.getFile() != null) {
+                in = FileUtils.openInputStream(pack.getFile());
+            }
+            // stay jcr 1.0 compatible
+            //noinspection deprecation
+            content.setProperty(JcrConstants.JCR_DATA, in);
+            if (pack != null) {
+                def.unwrap(pack, true, false);
+            }
+            if (autoSave) {
+                parent.save();
+            }
+        } finally {
+            IOUtils.closeQuietly(in);
+        }
+        return new JcrPackageImpl(node, (ZipVaultPackage) pack);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean verifyId(boolean autoFix, boolean autoSave) throws RepositoryException {
+        // check if package id is correct
+        JcrPackageDefinition jDef = getDefinition();
+        if (jDef == null) {
+            return true;
+        }
+        PackageId id = jDef.getId();
+        PackageId cId = new PackageId(node.getPath());
+        // compare installation paths since non-conform version numbers might
+        // lead to different pids (bug #35564)
+        if (id.getInstallationPath().equals(cId.getInstallationPath())) {
+            if (autoFix && id.isFromPath()) {
+                // if definition has no id set, fix anyways
+                jDef.setId(cId, autoSave);
+            }
+            return true;
+        }
+        if (autoFix) {
+            log.warn("Fixing non-matching id from {} to {}.", id, cId);
+            jDef.setId(cId, autoSave);
+        }
+        return false;
+    }
+
+
+    /**
+     * Tries to unwrap the definition of this package.
+     * @throws IOException if an I/O error occurs or if the underlying file is not a package
+     * @throws RepositoryException if a repository error occurs
+     */
+    public void tryUnwrap() throws IOException, RepositoryException {
+        if (isValid()) {
+            return;
+        }
+        VaultPackage pack = getPackage();
+        Node content = getContent();
+        boolean ok = false;
+        try {
+            content.addMixin(NT_VLT_PACKAGE);
+            Node defNode = content.addNode(NN_VLT_DEFINITION);
+            JcrPackageDefinition def = new JcrPackageDefinitionImpl(defNode);
+            def.unwrap(pack, true, false);
+            node.save();
+            ok = true;
+        } finally {
+            if (!ok) {
+                try {
+                    node.refresh(false);
+                } catch (RepositoryException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public VaultPackage getPackage() throws RepositoryException, IOException {
+        if (pack == null) {
+            File tmpFile = File.createTempFile("vaultpack", ".zip");
+            FileOutputStream out = FileUtils.openOutputStream(tmpFile);
+            // stay jcr 1.0 compatible
+            //noinspection deprecation
+            InputStream in = getData().getStream();
+            IOUtils.copy(in, out);
+            in.close();
+            out.close();
+            pack = new ZipVaultPackage(tmpFile, true);
+        }
+        return pack;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void extract(ImportOptions opts) throws RepositoryException, PackageException, IOException {
+        extract(opts, false, false);
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void install(ImportOptions opts) throws RepositoryException, PackageException, IOException {
+        extract(opts, true, false);
+    }
+
+    /**
+     * internally extracts the package.
+     *
+     * @param options the import options
+     * @param createSnapshot <code>true</code> if a snapshot should be created
+     * @param replaceSnapshot <code>true</code> if a snapshot should be replaced
+     * @throws RepositoryException if a repository error occurs
+     * @throws PackageException if a package error occurs
+     * @throws IOException if an I/O error occurs
+     */
+    private void extract(ImportOptions options, boolean createSnapshot, boolean replaceSnapshot)
+            throws RepositoryException, PackageException, IOException {
+        getPackage();
+        // get a copy of the import options (bug 35164)
+        ImportOptions opts = new ImportOptions(options);
+        // check for disable intermediate saves (GRANITE-1047)
+        if ( this.getDefinition().getBoolean(JcrPackageDefinition.PN_DISABLE_INTERMEDIATE_SAVE) ) {
+            // MAX_VALUE disables saving completely, therefore we have to use a lower value!
+            opts.setAutoSaveThreshold(Integer.MAX_VALUE - 1);
+        }
+        InstallContextImpl ctx = pack.prepareExtract(node.getSession(), opts);
+        JcrPackage snap = null;
+        if (!opts.isDryRun() && createSnapshot) {
+            ExportOptions eOpts = new ExportOptions();
+            eOpts.setListener(opts.getListener());
+            snap = snapshot(eOpts, replaceSnapshot, opts.getAccessControlHandling());
+        }
+        List<String> subPackages = new ArrayList<String>();
+        pack.extract(ctx, subPackages);
+        getDefinition();
+        if (def != null && !opts.isDryRun()) {
+            def.touchLastUnpacked(null, true);
+        }
+
+        // process sub packages
+        Session s = node.getSession();
+        List<JcrPackageImpl> subPacks = new LinkedList<JcrPackageImpl>();
+        for (String path: subPackages) {
+            if (s.nodeExists(path)) {
+                JcrPackageImpl p = new JcrPackageImpl(s.getNode(path));
+                if (!p.isValid()) {
+                    // check if package was included as pure .zip or .jar
+                    try {
+                        p.tryUnwrap();
+                    } catch (Exception e) {
+                        log.info("Sub package {} not valid: " + e, path);
+                    }
+                }
+                if (p.isValid()) {
+                    subPacks.add(p);
+                }
+            }
+        }
+
+        // don't extract sub packages if not recursive
+        if (!opts.isNonRecursive() && !subPacks.isEmpty()) {
+            try {
+                DependencyUtil.sortPackages(subPacks);
+            } catch (CyclicDependencyException e) {
+                if (opts.isStrict()) {
+                    throw e;
+                }
+            }
+            String[] subIds = new String[subPacks.size()];
+            int i=0;
+            for (JcrPackageImpl p: subPacks) {
+                String msg = "Starting extraction of subpackage " + p.getPackage().getId();
+                if (options.isDryRun()) {
+                    msg = "Dry run: " + msg;
+                }
+                if (options.getListener() != null) {
+                    options.getListener().onMessage(ProgressTrackerListener.Mode.TEXT, msg, "");
+                } else {
+                    log.info(msg);
+                }
+                p.extract(options, createSnapshot, true);
+                p.close();
+                subIds[i++] = p.getDefinition().getId().toString();
+            }
+            // register sub packages in snapshot for uninstall
+            if (snap != null) {
+                snap.getDefinition().getNode().setProperty(JcrPackageDefinition.PN_SUB_PACKAGES, subIds);
+                snap.getDefinition().getNode().save();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JcrPackage snapshot(ExportOptions opts, boolean replace)
+            throws RepositoryException, PackageException, IOException {
+        return snapshot(opts, replace, null);
+    }
+
+    /**
+     * Internally creates the snapshot
+     * @param opts exports options when building the snapshot
+     * @param replace if <code>true</code> existing snapshot will be replaced
+     * @param acHandling user acHandling to use when snapshot is installed, i.e. package is uninstalled
+     * @return the package of the snapshot or <code>null</code>
+     * @throws RepositoryException if an error occurrs.
+     * @throws PackageException if an error occurrs.
+     * @throws IOException if an error occurrs.
+     */
+    private JcrPackage snapshot(ExportOptions opts, boolean replace, AccessControlHandling acHandling)
+            throws RepositoryException, PackageException, IOException {
+        PackageId id = getSnapshotId();
+        Node packNode = getPackageNode(id);
+        if (packNode != null) {
+            if (!replace) {
+                log.warn("Refusing to recreate snapshot {}, already exists.", id);
+                return null;
+            } else {
+                packNode.remove();
+                node.getSession().save();
+            }
+        }
+        log.info("Creating snapshot for {}.", id);
+        JcrPackageManagerImpl packMgr = new JcrPackageManagerImpl(node.getSession());
+        String path = id.getInstallationPath();
+        String parentPath = Text.getRelativeParent(path, 1);
+        Node folder = packMgr.mkdir(parentPath, true);
+        JcrPackage snap = JcrPackageImpl.createNew(folder, id, null, true);
+        JcrPackageDefinitionImpl snapDef = (JcrPackageDefinitionImpl) snap.getDefinition();
+        JcrPackageDefinitionImpl myDef = (JcrPackageDefinitionImpl) getDefinition();
+        snapDef.setId(id, false);
+        snapDef.setFilter(myDef.getMetaInf().getFilter(), false);
+        snapDef.set(JcrPackageDefinition.PN_DESCRIPTION, "Snapshot of package " + myDef.getId().toString(), false);
+        if (acHandling == null) {
+            snapDef.set(JcrPackageDefinition.PN_AC_HANDLING, myDef.get(JcrPackageDefinition.PN_AC_HANDLING), false);
+        } else {
+            snapDef.set(JcrPackageDefinition.PN_AC_HANDLING, acHandling.name(), false);
+        }
+        if (opts.getListener() != null) {
+            opts.getListener().onMessage(ProgressTrackerListener.Mode.TEXT, "Creating snapshot for package " + myDef.getId(), "");
+        }
+        packMgr.assemble(snap.getNode(), snapDef, opts.getListener());
+        log.info("Creating snapshot for {} completed.", id);
+        return snap;
+    }
+
+    /**
+     * Returns the package node of the given package id.
+     * @param id the package id
+     * @return the package node
+     * @throws RepositoryException if an error occurs
+     */
+    private Node getPackageNode(PackageId id) throws RepositoryException {
+        if (node.getSession().nodeExists(id.getInstallationPath())) {
+            return node.getSession().getNode(id.getInstallationPath());
+        } else if (node.getSession().nodeExists(id.getInstallationPath() + ".zip")) {
+            return node.getSession().getNode(id.getInstallationPath() + ".zip");
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JcrPackage getSnapshot() throws RepositoryException {
+        PackageId id = getSnapshotId();
+        Node packNode = getPackageNode(id);
+        if (packNode != null) {
+            JcrPackageImpl snap = new JcrPackageImpl(packNode);
+            if (snap.isValid()) {
+                return snap;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the snapshot id of this package.
+     * @return the snapshot package id
+     * @throws RepositoryException if an error occurs
+     */
+    private PackageId getSnapshotId() throws RepositoryException {
+        PackageId id = getDefinition().getId();
+        String group = id.getGroup();
+        if (group.length() == 0) {
+            group = ".snapshot";
+        } else {
+            group += "/.snapshot";
+        }
+        return new PackageId(
+                group,
+                id.getName(),
+                id.getVersion());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void uninstall(ImportOptions opts) throws RepositoryException, PackageException, IOException {
+        JcrPackage snap = getSnapshot();
+        if (snap == null) {
+            throw new PackageException("Unable to uninstall package. No snapshot present.");
+        }
+        if (opts.getListener() != null) {
+            opts.getListener().onMessage(ProgressTrackerListener.Mode.TEXT, "Uninstalling package from snapshot " + snap.getDefinition().getId(), "");
+        }
+        Session s = getNode().getSession();
+        // check for recursive unininstall
+        if (!opts.isNonRecursive()) {
+            Node defNode = snap.getDefNode();
+            LinkedList<PackageId> subPackages = new LinkedList<PackageId>();
+            if (defNode.hasProperty(JcrPackageDefinition.PN_SUB_PACKAGES)) {
+                Value[] subIds = defNode.getProperty(JcrPackageDefinition.PN_SUB_PACKAGES).getValues();
+                for (Value v: subIds) {
+                    // reverse installation order
+                    subPackages.addLast(PackageId.fromString(v.getString()));
+                }
+            }
+            if (subPackages.size() > 0) {
+                JcrPackageManagerImpl packMgr = new JcrPackageManagerImpl(s);
+                for (PackageId id: subPackages) {
+                    JcrPackage pack = packMgr.open(id);
+                    if (pack != null) {
+                        pack.uninstall(opts);
+                    }
+                }
+
+            }
+        }
+        // override import mode
+        opts.setImportMode(ImportMode.REPLACE);
+        snap.extract(opts);
+        snap.getNode().remove();
+        s.save();
+        // revert installed flags on this package
+        JcrPackageDefinitionImpl def = (JcrPackageDefinitionImpl) getDefinition();
+        def.clearLastUnpacked(true);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getSize() {
+        try {
+            assertValid();
+            return getData().getLength();
+        } catch (RepositoryException e) {
+            log.error("Error during getSize()", e);
+            return -1;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void close() {
+        node = null;
+        if (pack != null) {
+            pack.close();
+            pack = null;
+        }
+    }
+
+    /**
+     * Returns the jcr:content node
+     * @return the jcr:content node
+     * @throws RepositoryException if an error occurrs
+     */
+    private Node getContent() throws RepositoryException {
+        return node.getNode(JcrConstants.JCR_CONTENT);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Property getData() throws RepositoryException {
+        return getContent().getProperty(JcrConstants.JCR_DATA);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Node getDefNode() throws RepositoryException {
+        Node content = getContent();
+        return content.hasNode(NN_VLT_DEFINITION)
+                ? content.getNode(NN_VLT_DEFINITION)
+                : null;
+    }
+
+    /**
+     * Ensures that the package is valid.
+     * @throws RepositoryException if an error occurs
+     */
+    private void assertValid() throws RepositoryException {
+        if (!isValid()) {
+            throw new IllegalArgumentException("not a valid package.");
+        }
+    }
+}
\ No newline at end of file



Mime
View raw message