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 2013/08/10 07:53:54 UTC

svn commit: r1512568 [21/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...

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageManagerImpl.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageManagerImpl.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrPackageManagerImpl.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,677 @@
+/*
+ * 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.InputStreamReader;
+import java.io.OutputStream;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.jcr.ItemExistsException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.vault.fs.api.ProgressTrackerListener;
+import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
+import org.apache.jackrabbit.vault.fs.spi.CNDReader;
+import org.apache.jackrabbit.vault.fs.spi.NodeTypeInstaller;
+import org.apache.jackrabbit.vault.fs.spi.ServiceProviderFactory;
+import org.apache.jackrabbit.vault.packaging.Dependency;
+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.JcrPackageManager;
+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.packaging.Version;
+import org.apache.jackrabbit.vault.util.JcrConstants;
+import org.apache.jackrabbit.vault.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extends the <code>PackageManager</code> by JCR specific methods
+ */
+public class JcrPackageManagerImpl extends PackageManagerImpl implements JcrPackageManager {
+
+    /**
+     * default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(JcrPackageManagerImpl.class);
+
+    /**
+     * name of node types resource
+     */
+    private static final String DEFAULT_NODETYPES = "nodetypes.cnd";
+
+    /**
+     * suggested folder types
+     */
+    private final static String[] FOLDER_TYPES = {"sling:Folder", "nt:folder", "nt:unstructured", null};
+
+    /**
+     * internal session
+     */
+    private final Session session;
+
+    /**
+     * package root (/etc/packages)
+     */
+    private Node packRoot;
+
+    /**
+     * Creates a new package manager using the given session.
+     *
+     * @param session repository session
+     */
+    public JcrPackageManagerImpl(Session session) {
+        this.session = session;
+        initNodeTypes();
+    }
+
+    public JcrPackage open(PackageId id) throws RepositoryException {
+        String path = id.getInstallationPath();
+        String[] exts = new String[]{"", ".zip", ".jar"};
+        for (String ext: exts) {
+            if (session.nodeExists(path + ext)) {
+                return open(session.getNode(path + ext));
+            }
+        }
+        return null;
+    }
+
+    public JcrPackage open(Node node) throws RepositoryException {
+        return open(node, false);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JcrPackage open(Node node, boolean allowInvalid) throws RepositoryException {
+        JcrPackage pack = new JcrPackageImpl(node);
+        if (pack.isValid()) {
+            return pack;
+        } else if (allowInvalid
+                && node.isNodeType(JcrConstants.NT_HIERARCHYNODE)
+                && node.hasProperty(JcrConstants.JCR_CONTENT + "/" + JcrConstants.JCR_DATA)) {
+            return pack;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public PackageId resolve(Dependency dependency, boolean onlyInstalled) throws RepositoryException {
+        if (!getPackageRoot().hasNode(dependency.getGroup())) {
+            return null;
+        }
+        Node groupNode = getPackageRoot().getNode(dependency.getGroup());
+        NodeIterator iter = groupNode.getNodes();
+        PackageId bestId = null;
+        while (iter.hasNext()) {
+            Node child = iter.nextNode();
+            if (child.getName().equals(".snapshot")) {
+                continue;
+            }
+            JcrPackageImpl pack = new JcrPackageImpl(child);
+            if (pack.isValid()) {
+                if (onlyInstalled && !pack.isInstalled()) {
+                    continue;
+                }
+                PackageId id = pack.getDefinition().getId();
+                if (dependency.matches(id)) {
+                    if (bestId == null || id.getVersion().compareTo(bestId.getVersion()) > 0) {
+                        bestId = id;
+                    }
+                }
+            }
+
+        }
+        return bestId;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JcrPackage upload(InputStream in, boolean replace) throws RepositoryException, IOException {
+        return upload(in, replace, false);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JcrPackage upload(InputStream in, boolean replace, boolean strict)
+            throws RepositoryException, IOException {
+        File file = File.createTempFile("vault", ".tmp");
+        OutputStream out = FileUtils.openOutputStream(file);
+        try {
+            IOUtils.copy(in, out);
+        } finally {
+            IOUtils.closeQuietly(in);
+            IOUtils.closeQuietly(out);
+        }
+        return upload(file, true, replace, null, strict);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JcrPackage upload(File file, boolean isTmpFile, boolean replace, String nameHint)
+            throws RepositoryException, IOException {
+        return upload(file, isTmpFile, replace, nameHint, false);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JcrPackage upload(File file, boolean isTmpFile, boolean replace, String nameHint, boolean strict)
+            throws RepositoryException, IOException {
+
+        // open zip packages
+        ZipVaultPackage pack = new ZipVaultPackage(file, isTmpFile, strict);
+        if (pack.getArchive().getJcrRoot() == null) {
+            String msg = "Zip File is not a content package. Missing 'jcr_root'.";
+            log.error(msg);
+            pack.close();
+            throw new IOException(msg);
+        }
+
+        // invalidate pid if path is unknown
+        PackageId pid = pack.getId();
+        if (pid != null && pid.getInstallationPath().equals(ZipVaultPackage.UNKNOWN_PATH)) {
+            pid = null;
+        }
+        if (pid == null) {
+            if (nameHint == null || nameHint.length() == 0) {
+                throw new IOException("Package does not contain a path specification and not name hint is given.");
+            }
+            pid = new PackageId(nameHint);
+        }
+        // create parent node
+        String path = pid.getInstallationPath() + ".zip";
+        String parentPath = Text.getRelativeParent(path, 1);
+        String name = Text.getName(path);
+        Node parent = mkdir(parentPath, false);
+
+        // remember installation state properties (GRANITE-2018)
+        JcrPackageDefinitionImpl.State state = null;
+
+        if (parent.hasNode(name)) {
+            JcrPackage oldPackage = new JcrPackageImpl(parent.getNode(name));
+            JcrPackageDefinitionImpl oldDef = (JcrPackageDefinitionImpl) oldPackage.getDefinition();
+            if (oldDef != null) {
+                state = oldDef.getState();
+            }
+
+            if (replace) {
+                parent.getNode(name).remove();
+            } else {
+                throw new ItemExistsException("Package already exists: " + path);
+            }
+        }
+        JcrPackage jcrPack = null;
+        try {
+            jcrPack = JcrPackageImpl.createNew(parent, pid, pack, false);
+            if (jcrPack != null) {
+                JcrPackageDefinitionImpl def = (JcrPackageDefinitionImpl) jcrPack.getDefinition();
+                if (state != null) {
+                    def.setState(state);
+                }
+            }
+            return jcrPack;
+        } finally {
+            if (jcrPack == null) {
+                session.refresh(false);
+            } else {
+                session.save();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JcrPackage create(Node folder, String name)
+            throws RepositoryException, IOException {
+        if (folder == null) {
+            folder = getPackageRoot();
+        }
+        return JcrPackageImpl.createNew(folder, new PackageId(name), null, true);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JcrPackage create(String group, String name)
+            throws RepositoryException, IOException {
+        return create(group, name, null);
+    }
+    /**
+     * {@inheritDoc}
+     */
+    public JcrPackage create(String group, String name, String version)
+            throws RepositoryException, IOException {
+        // sanitize name
+        String ext = Text.getName(name, '.');
+        if (ext.equals("zip") || ext.equals("jar")) {
+            name = name.substring(0, name.length() - 4);
+        }
+        PackageId pid = new PackageId(group, name, version);
+        Node folder = mkdir(Text.getRelativeParent(pid.getInstallationPath(), 1), false);
+        try {
+            return JcrPackageImpl.createNew(folder, pid, null, false);
+        } finally {
+            session.save();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void remove(JcrPackage pack) throws RepositoryException {
+        JcrPackage snap = pack.getSnapshot();
+        if (snap != null) {
+            snap.getNode().remove();
+        }
+        pack.getNode().remove();
+        session.save();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JcrPackage rename(JcrPackage pack, String group, String name)
+            throws PackageException, RepositoryException {
+        return rename(pack, group, name, null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JcrPackage rename(JcrPackage pack, String group, String name, String version)
+            throws PackageException, RepositoryException {
+        if (!pack.isValid()) {
+            throw new PackageException("Package is not valid.");
+        }
+        if (pack.getSize() > 0 && !pack.getDefinition().isUnwrapped()) {
+            throw new PackageException("Package definition not unwrapped.");
+        }
+        JcrPackageDefinition def = pack.getDefinition();
+        PackageId id = def.getId();
+        PackageId newId = new PackageId(
+                group == null ? id.getGroup() : group,
+                name == null ? id.getName() : name,
+                version == null ? id.getVersion() : Version.create(version)
+        );
+        String dstPath = newId.getInstallationPath() + ".zip";
+        if (id.equals(newId) && pack.getNode().getPath().equals(dstPath)) {
+            log.info("Package id not changed. won't rename.");
+            return pack;
+        }
+        def.setId(newId, false);
+
+        // only move if not already at correct location
+        if (!pack.getNode().getPath().equals(dstPath)) {
+            if (session.nodeExists(dstPath)) {
+                throw new PackageException("Node at " + dstPath + " already exists.");
+            }
+            // ensure parent path exists
+            mkdir(Text.getRelativeParent(dstPath, 1), false);
+            session.move(pack.getNode().getPath(), dstPath);
+        }
+
+        session.save();
+        Node newNode = session.getRootNode().getNode(dstPath.substring(1));
+        return open(newNode);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void assemble(JcrPackage pack, ProgressTrackerListener listener)
+            throws PackageException, RepositoryException, IOException {
+        pack.verifyId(true, true);
+        assemble(pack.getNode(), pack.getDefinition(), listener);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void assemble(Node packNode, JcrPackageDefinition definition,
+                         ProgressTrackerListener listener)
+            throws PackageException, RepositoryException, IOException {
+        Calendar now = Calendar.getInstance();
+        JcrPackageDefinitionImpl def = (JcrPackageDefinitionImpl) definition;
+        validateSubPackages(def);
+        def.sealForAssembly(now, true);
+
+        ExportOptions opts = new ExportOptions();
+        opts.setMetaInf(def.getMetaInf());
+        opts.setListener(listener);
+        opts.setPostProcessor(def.getInjectProcessor());
+
+        VaultPackage pack = assemble(packNode.getSession(), opts, (File) null);
+
+        // update this content
+        Node contentNode = packNode.getNode(JcrConstants.JCR_CONTENT);
+        InputStream in;
+        try {
+            in = FileUtils.openInputStream(pack.getFile());
+        } catch (IOException e) {
+            throw new PackageException(e);
+        }
+        // stay jcr 1.0 compatible
+        //noinspection deprecation
+        contentNode.setProperty(JcrConstants.JCR_DATA, in);
+        contentNode.setProperty(JcrConstants.JCR_LASTMODIFIED, now);
+        contentNode.setProperty(JcrConstants.JCR_MIMETYPE, JcrPackage.MIME_TYPE);
+        packNode.save();
+        pack.close();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    private void validateSubPackages(JcrPackageDefinitionImpl def)
+            throws RepositoryException, PackageException {
+        List<JcrPackage> subs = listPackages(def.getMetaInf().getFilter());
+        PackageId id = def.getId();
+        for (JcrPackage p: subs) {
+            // check if not include itself
+            if (p.getDefinition().getId().equals(id)) {
+                throw new PackageException("A package cannot include itself. Check filter definition.");
+            }
+            if (!p.isSealed()) {
+                throw new PackageException("Only sealed (built) sub packages allowed: " + p.getDefinition().getId());
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void assemble(JcrPackageDefinition definition,
+                         ProgressTrackerListener listener, OutputStream out)
+            throws IOException, RepositoryException, PackageException {
+        JcrPackageDefinitionImpl def = (JcrPackageDefinitionImpl) definition;
+        validateSubPackages(def);
+        Calendar now = Calendar.getInstance();
+        def.sealForAssembly(now, true);
+
+        ExportOptions opts = new ExportOptions();
+        opts.setMetaInf(def.getMetaInf());
+        opts.setListener(listener);
+        opts.setPostProcessor(def.getInjectProcessor());
+
+        assemble(def.getNode().getSession(), opts, out);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void rewrap(JcrPackage pack, ProgressTrackerListener listener)
+            throws PackageException, RepositoryException, IOException {
+        VaultPackage src = pack.getPackage();
+
+        Calendar now = Calendar.getInstance();
+        pack.verifyId(true, false);
+        JcrPackageDefinitionImpl def = (JcrPackageDefinitionImpl) pack.getDefinition();
+        def.sealForRewrap(now, true);
+
+        ExportOptions opts = new ExportOptions();
+        opts.setMetaInf(def.getMetaInf());
+        opts.setListener(listener);
+        opts.setPostProcessor(def.getInjectProcessor());
+
+        VaultPackage dst = rewrap(opts, src, (File) null);
+
+        // update this content
+        Node packNode = pack.getNode();
+        Node contentNode = packNode.getNode(JcrConstants.JCR_CONTENT);
+        InputStream in;
+        try {
+            in = FileUtils.openInputStream(dst.getFile());
+        } catch (IOException e) {
+            throw new PackageException(e);
+        }
+        // stay jcr 1.0 compatible
+        //noinspection deprecation
+        contentNode.setProperty(JcrConstants.JCR_DATA, in);
+        contentNode.setProperty(JcrConstants.JCR_LASTMODIFIED, now);
+        contentNode.setProperty(JcrConstants.JCR_MIMETYPE, JcrPackage.MIME_TYPE);
+        packNode.save();
+        dst.close();
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Node getPackageRoot() throws RepositoryException {
+        return getPackageRoot(false);
+    }
+
+    /**
+     * yet another Convenience method to create intermediate nodes.
+     * @param path path to create
+     * @param autoSave if <code>true</code> all changes are automatically persisted
+     * @return the node
+     * @throws RepositoryException if an error occurrs
+     */
+    protected Node mkdir(String path, boolean autoSave) throws RepositoryException {
+        if (session.nodeExists(path)) {
+            return session.getNode(path);
+        }
+        String parentPath = Text.getRelativeParent(path, 1);
+        if (path == null || (path.equals("/") && parentPath.equals(path))) {
+            throw new RepositoryException("could not crete intermediate nodes");
+        }
+        Node parent = mkdir(parentPath, autoSave);
+        Node node = null;
+        RepositoryException lastError = null;
+        for (int i=0; node == null && i<FOLDER_TYPES.length; i++) {
+            try {
+                node = parent.addNode(Text.getName(path), FOLDER_TYPES[i]);
+            } catch (RepositoryException e) {
+                lastError = e;
+            }
+        }
+        if (node == null) {
+            throw lastError;
+        }
+        if (autoSave) {
+            parent.save();
+        }
+        return node;
+    }
+
+    /**
+     * Initializes vlt node types (might not be the correct location)
+     */
+    private void initNodeTypes() {
+        // check if node types are registered
+        try {
+            session.getWorkspace().getNodeTypeManager().getNodeType(JcrPackage.NT_VLT_PACKAGE);
+            // also check/register nodetypes needed for assembly
+            session.getWorkspace().getNodeTypeManager().getNodeType("vlt:HierarchyNode");
+            session.getWorkspace().getNodeTypeManager().getNodeType("vlt:FullCoverage");
+            return;
+        } catch (RepositoryException e) {
+            // ignore
+        }
+        InputStream in = null;
+        try {
+            in = getClass().getResourceAsStream(DEFAULT_NODETYPES);
+            if (in == null) {
+                throw new InternalError("Could not load " + DEFAULT_NODETYPES + " resource.");
+            }
+            NodeTypeInstaller installer = ServiceProviderFactory.getProvider().getDefaultNodeTypeInstaller(session);
+            CNDReader types = ServiceProviderFactory.getProvider().getCNDReader();
+            types.read(new InputStreamReader(in, "utf8"), DEFAULT_NODETYPES, null);
+            installer.install(null, types);
+        } catch (Throwable e) {
+            log.warn("Error while registering nodetypes. Package installation might not work correctly.", e);
+        } finally {
+            IOUtils.closeQuietly(in);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Node getPackageRoot(boolean noCreate) throws RepositoryException {
+        if (this.packRoot == null) {
+            Node packRoot = session.getRootNode();
+            if (packRoot.hasNode("etc")) {
+                packRoot = packRoot.getNode("etc");
+            } else {
+                if (noCreate) {
+                    return null;
+                }
+                if (packRoot.isModified()) {
+                    throw new RepositoryException("Unwilling to create package root folder while session has transient changes.");
+                }
+                packRoot = packRoot.addNode("etc", JcrConstants.NT_FOLDER);
+            }
+            if (packRoot.hasNode("packages")) {
+                packRoot = packRoot.getNode("packages");
+            } else {
+                if (noCreate) {
+                    return null;
+                }
+                packRoot = packRoot.addNode("packages", JcrConstants.NT_FOLDER);
+                try {
+                    session.save();
+                } catch (RepositoryException e) {
+                    try {
+                        session.refresh(false);
+                    } catch (RepositoryException e1) {
+                        // ignore and re-throw original exception
+                    }
+                    throw e;
+                }
+            }
+            this.packRoot = packRoot;
+        }
+        return this.packRoot;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<JcrPackage> listPackages() throws RepositoryException {
+        return listPackages(null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<JcrPackage> listPackages(WorkspaceFilter filter) throws RepositoryException {
+        Node root = getPackageRoot(true);
+        if (root == null) {
+            return Collections.emptyList();
+        } else {
+            List<JcrPackage> packages = new LinkedList<JcrPackage>();
+            listPackages(root, packages, filter, false, false);
+            Collections.sort(packages);
+            return packages;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<JcrPackage> listPackages(String group, boolean built) throws RepositoryException {
+        Node pRoot = getPackageRoot(true);
+        if (pRoot == null) {
+            return Collections.emptyList();
+        }
+        List<JcrPackage> packages = new LinkedList<JcrPackage>();
+        if (group == null) {
+            listPackages(pRoot, packages, null, built, false);
+        } else {
+            Node root = pRoot;
+            if (group.length() > 0) {
+                if (group.equals(pRoot.getPath())) {
+                    group = "";
+                } else if (group.startsWith(pRoot.getPath() + "/")) {
+                    group = group.substring(pRoot.getPath().length() + 1);
+                }
+                if (group.startsWith("/")) {
+                    // don't scan outside /etc/packages
+                    return packages;
+                } else if (group.length() > 0) {
+                    if (root.hasNode(group)) {
+                        root = root.getNode(group);
+                    } else {
+                        return packages;
+                    }
+                }
+            }
+            listPackages(root, packages, null, built, true);
+        }
+        Collections.sort(packages);
+        return packages;
+    }
+
+    /**
+     * internally adds the packages below <code>root</code> to the given list
+     * recursively.
+     *
+     * @param root root node
+     * @param packages list for the packages
+     * @param filter optional filter to filter out packages
+     * @param built if <code>true</code> only packages with size > 0 are returned
+     * @param shallow if <code>true</code> don't recurs
+     * @throws RepositoryException if an error occurs
+     */
+    private void listPackages(Node root, List<JcrPackage> packages,
+                              WorkspaceFilter filter, boolean built, boolean shallow)
+            throws RepositoryException {
+        if (root != null) {
+            for (NodeIterator iter = root.getNodes(); iter.hasNext();) {
+                Node child = iter.nextNode();
+                if (child.getName().equals(".snapshot")) {
+                    continue;
+                }
+                JcrPackageImpl pack = new JcrPackageImpl(child);
+                if (pack.isValid()) {
+                    if (filter == null || filter.contains(child.getPath())) {
+                        if (!built || pack.getSize() > 0) {
+                            packages.add(pack);
+                        }
+                    }
+                } else if (child.hasNodes() && !shallow){
+                    listPackages(child, packages, filter, built, shallow);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrWorkspaceFilter.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrWorkspaceFilter.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrWorkspaceFilter.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/JcrWorkspaceFilter.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,155 @@
+/*
+ * 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.util.LinkedList;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.vault.fs.api.ImportMode;
+import org.apache.jackrabbit.vault.fs.api.ItemFilterSet;
+import org.apache.jackrabbit.vault.fs.api.PathFilterSet;
+import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
+import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
+import org.apache.jackrabbit.vault.fs.filter.DefaultPathFilter;
+
+/**
+ * <code>JcrWorkspaceFilter</code> implements serializations of a workspace
+ * filter that is stored in the repository
+ */
+public class JcrWorkspaceFilter  {
+
+    public static DefaultWorkspaceFilter loadFilter(Node defNode) throws RepositoryException {
+        DefaultWorkspaceFilter wsp = new DefaultWorkspaceFilter();
+        if (defNode.hasNode(JcrPackageDefinitionImpl.NN_FILTER)) {
+            defNode = defNode.getNode(JcrPackageDefinitionImpl.NN_FILTER);
+        }
+        for (NodeIterator filters = defNode.getNodes(); filters.hasNext();) {
+            Node filter = filters.nextNode();
+            String root = filter.hasProperty(JcrPackageDefinitionImpl.PN_ROOT)
+                    ? filter.getProperty(JcrPackageDefinitionImpl.PN_ROOT).getString()
+                    : "";
+            if (root.length() == 0) {
+                continue;
+            }
+            String mode = filter.hasProperty(JcrPackageDefinitionImpl.PN_MODE)
+                    ? filter.getProperty(JcrPackageDefinitionImpl.PN_MODE).getString()
+                    : "";
+            PathFilterSet set = new PathFilterSet(root);
+            if (mode.length() > 0) {
+                set.setImportMode(ImportMode.valueOf(mode.toUpperCase()));
+            }
+            if (filter.hasProperty(JcrPackageDefinitionImpl.PN_RULES)) {
+                // new version with mv rules property
+                Property p = filter.getProperty(JcrPackageDefinitionImpl.PN_RULES);
+                Value[] values = p.getDefinition().isMultiple() ? p.getValues() : new Value[]{p.getValue()};
+                for (Value value: values) {
+                    String rule = value.getString();
+                    int idx = rule.indexOf(':');
+                    String type = idx > 0 ? rule.substring(0, idx) : "include";
+                    String patt = idx > 0 ? rule.substring(idx + 1) : "";
+                    DefaultPathFilter pf = new DefaultPathFilter(patt);
+                    if (type.equals("include")) {
+                        set.addInclude(pf);
+                    } else {
+                        set.addExclude(pf);
+                    }
+                }
+            } else {
+                for (NodeIterator rules = filter.getNodes(); rules.hasNext();) {
+                    Node rule = rules.nextNode();
+                    String type = rule.getProperty(JcrPackageDefinitionImpl.PN_TYPE).getString();
+                    String pattern = rule.getProperty(JcrPackageDefinitionImpl.PN_PATTERN).getString();
+                    DefaultPathFilter pf = new DefaultPathFilter(pattern);
+                    if (type.equals("include")) {
+                        set.addInclude(pf);
+                    } else {
+                        set.addExclude(pf);
+                    }
+                }
+            }
+            wsp.add(set);
+        }
+        return wsp;
+    }
+
+    public static void saveLegacyFilter(WorkspaceFilter filter, Node defNode, boolean save)
+            throws RepositoryException {
+        // delete all nodes first
+        for (NodeIterator iter = defNode.getNodes(); iter.hasNext();) {
+        iter.nextNode().remove();
+        }
+        int nr = 0;
+        for (PathFilterSet set: filter.getFilterSets()) {
+            Node setNode = defNode.addNode("f" + nr);
+            setNode.setProperty(JcrPackageDefinitionImpl.PN_ROOT, set.getRoot());
+            int eNr = 0;
+            for (ItemFilterSet.Entry e: set.getEntries()) {
+                // expect path filter
+                if (!(e.getFilter() instanceof DefaultPathFilter)) {
+                    throw new IllegalArgumentException("Can only handle default path filters.");
+                }
+                Node eNode = setNode.addNode("f" + eNr);
+                eNode.setProperty(JcrPackageDefinitionImpl.PN_TYPE, e.isInclude() ? "include" : "exclude");
+                eNode.setProperty(JcrPackageDefinitionImpl.PN_PATTERN, ((DefaultPathFilter) e.getFilter()).getPattern());
+                eNr++;
+            }
+            nr++;
+        }
+        if (save) {
+            defNode.save();
+        }
+    }
+
+
+    public static void saveFilter(WorkspaceFilter filter, Node defNode, boolean save)
+            throws RepositoryException {
+        if (defNode.hasNode(JcrPackageDefinitionImpl.NN_FILTER)) {
+            defNode.getNode(JcrPackageDefinitionImpl.NN_FILTER).remove();
+        }
+        Node filterNode = defNode.addNode(JcrPackageDefinitionImpl.NN_FILTER);
+        int nr = 0;
+        for (PathFilterSet set: filter.getFilterSets()) {
+            Node setNode = filterNode.addNode("f" + nr);
+            setNode.setProperty(JcrPackageDefinitionImpl.PN_ROOT, set.getRoot());
+            setNode.setProperty(JcrPackageDefinitionImpl.PN_MODE, set.getImportMode().name().toLowerCase());
+            List<String> rules = new LinkedList<String>();
+            for (ItemFilterSet.Entry e: set.getEntries()) {
+                // expect path filter
+                if (!(e.getFilter() instanceof DefaultPathFilter)) {
+                    throw new IllegalArgumentException("Can only handle default path filters.");
+                }
+                String type = e.isInclude() ? "include" : "exclude";
+                String patt = ((DefaultPathFilter) e.getFilter()).getPattern();
+                rules.add(type + ":" + patt);
+            }
+            setNode.setProperty(JcrPackageDefinitionImpl.PN_RULES, rules.toArray(new String[rules.size()]));
+            nr++;
+        }
+        if (save) {
+            defNode.save();
+        }
+    }
+
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerImpl.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerImpl.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerImpl.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,217 @@
+/*
+ * 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.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URISyntaxException;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.vault.fs.Mounter;
+import org.apache.jackrabbit.vault.fs.api.RepositoryAddress;
+import org.apache.jackrabbit.vault.fs.api.VaultFileSystem;
+import org.apache.jackrabbit.vault.fs.config.DefaultMetaInf;
+import org.apache.jackrabbit.vault.fs.config.MetaInf;
+import org.apache.jackrabbit.vault.fs.io.JarExporter;
+import org.apache.jackrabbit.vault.fs.spi.ProgressTracker;
+import org.apache.jackrabbit.vault.packaging.ExportOptions;
+import org.apache.jackrabbit.vault.packaging.PackageManager;
+import org.apache.jackrabbit.vault.packaging.VaultPackage;
+import org.apache.jackrabbit.vault.util.Constants;
+
+/**
+ * Implements the package manager
+ */
+public class PackageManagerImpl implements PackageManager {
+
+    /**
+     * {@inheritDoc}
+     */
+    public VaultPackage open(File file) throws IOException {
+        return open(file, false);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public VaultPackage open(File file, boolean strict) throws IOException {
+        return new ZipVaultPackage(file, false, strict);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public VaultPackage assemble(Session s, ExportOptions opts, File file)
+            throws IOException, RepositoryException {
+        OutputStream out = null;
+        boolean isTmp = false;
+        boolean success = false;
+        try {
+            if (file == null) {
+                file = File.createTempFile("filevault", ".zip");
+                isTmp = true;
+            }
+            out = FileUtils.openOutputStream(file);
+            assemble(s, opts, out);
+            IOUtils.closeQuietly(out);
+            success = true;
+            return new ZipVaultPackage(file, isTmp);
+        } finally {
+            IOUtils.closeQuietly(out);
+            if (isTmp && !success) {
+                FileUtils.deleteQuietly(file);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void assemble(Session s, ExportOptions opts, OutputStream out)
+            throws IOException, RepositoryException {
+        RepositoryAddress addr;
+        try {
+            String mountPath = opts.getMountPath();
+            if (mountPath == null || mountPath.length() == 0) {
+                mountPath = "/";
+            }
+            addr = new RepositoryAddress("/" + s.getWorkspace().getName() + mountPath);
+        } catch (URISyntaxException e) {
+            throw new IllegalArgumentException(e);
+        }
+        MetaInf metaInf = opts.getMetaInf();
+        if (metaInf == null) {
+            metaInf = new DefaultMetaInf();
+        }
+        VaultFileSystem jcrfs = Mounter.mount(metaInf.getConfig(), metaInf.getFilter(), addr, opts.getRootPath(), s);
+        JarExporter exporter = new JarExporter(out);
+        exporter.setProperties(metaInf.getProperties());
+        if (opts.getListener() != null) {
+            exporter.setVerbose(opts.getListener());
+        }
+        if (opts.getPostProcessor() != null) {
+            exporter.export(jcrfs.getRoot(), true);
+            opts.getPostProcessor().process(exporter);
+            exporter.close();
+        } else {
+            exporter.export(jcrfs.getRoot());
+        }
+        jcrfs.unmount();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public VaultPackage rewrap(ExportOptions opts, VaultPackage src, File file)
+            throws IOException, RepositoryException {
+        OutputStream out = null;
+        boolean isTmp = false;
+        boolean success = false;
+        try {
+            if (file == null) {
+                file = File.createTempFile("filevault", ".zip");
+                isTmp = true;
+            }
+            out = FileUtils.openOutputStream(file);
+            rewrap(opts, src, out);
+            IOUtils.closeQuietly(out);
+            success = true;
+            return new ZipVaultPackage(file, isTmp);
+        } finally {
+            IOUtils.closeQuietly(out);
+            if (isTmp && !success) {
+                FileUtils.deleteQuietly(file);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void rewrap(ExportOptions opts, VaultPackage src, OutputStream out)
+            throws IOException {
+        MetaInf metaInf = opts.getMetaInf();
+        if (metaInf == null) {
+            metaInf = new DefaultMetaInf();
+        }
+        JarExporter exporter = new JarExporter(out);
+        exporter.open();
+        exporter.setProperties(metaInf.getProperties());
+        ProgressTracker tracker = null;
+        if (opts.getListener() != null) {
+            tracker = new ProgressTracker();
+            exporter.setVerbose(opts.getListener());
+        }
+
+        // merge
+        MetaInf inf = opts.getMetaInf();
+        ZipFile zip = new ZipFile(src.getFile(), ZipFile.OPEN_READ);
+        if (opts.getPostProcessor() == null) {
+            // no post processor, we keep all files except the properties
+            Enumeration e = zip.entries();
+            while (e.hasMoreElements()) {
+                ZipEntry entry = (ZipEntry) e.nextElement();
+                String path = entry.getName();
+                if (!path.equals(Constants.META_DIR + "/" + Constants.PROPERTIES_XML)) {
+                    exporter.write(zip, entry);
+                }
+            }
+        } else {
+            Set<String> keep = new HashSet<String>();
+            keep.add(Constants.META_DIR + "/");
+            keep.add(Constants.META_DIR + "/" + Constants.NODETYPES_CND);
+            keep.add(Constants.META_DIR + "/" + Constants.CONFIG_XML);
+            keep.add(Constants.META_DIR + "/" + Constants.FILTER_XML);
+            Enumeration e = zip.entries();
+            while (e.hasMoreElements()) {
+                ZipEntry entry = (ZipEntry) e.nextElement();
+                String path = entry.getName();
+                if (!path.startsWith(Constants.META_DIR + "/") || keep.contains(path)) {
+                    exporter.write(zip, entry);
+                }
+            }
+        }
+        zip.close();
+
+        // write updated properties
+        ByteArrayOutputStream tmpOut = new ByteArrayOutputStream();
+        inf.getProperties().storeToXML(tmpOut, "FileVault Package Properties", "utf-8");
+        exporter.writeFile(new ByteArrayInputStream(tmpOut.toByteArray()), Constants.META_DIR + "/" + Constants.PROPERTIES_XML);
+        if (tracker != null) {
+            tracker.track("A", Constants.META_DIR + "/" + Constants.PROPERTIES_XML);
+        }
+
+        if (opts.getPostProcessor() != null) {
+            opts.getPostProcessor().process(exporter);
+        }
+        exporter.close();
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerMBean.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerMBean.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerMBean.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerMBean.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,28 @@
+/*
+ * 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 javax.management.openmbean.TabularData;
+
+/**
+ * <code>PackageManagerMBean</code>...
+ */
+public interface PackageManagerMBean {
+
+    TabularData getPackages();
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerMBeanImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerMBeanImpl.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerMBeanImpl.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerMBeanImpl.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,151 @@
+/*
+ * 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 javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.management.DynamicMBean;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanInfo;
+import javax.management.NotCompliantMBeanException;
+import javax.management.StandardMBean;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.jackrabbit.vault.packaging.JcrPackage;
+import org.apache.jackrabbit.vault.packaging.JcrPackageManager;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>PackageManagerMBeanImpl</code> provide a MBean that lists all available packages as tabular data.
+ * it uses the "com.adobe.granite.packaging" to better fit into the granite hierarchy.
+ */
+@Component()
+@Property(name = "jmx.objectname", value="com.adobe.granite.packaging:type=manager")
+@Service(value = DynamicMBean.class)
+public class PackageManagerMBeanImpl extends StandardMBean implements PackageManagerMBean {
+
+    /**
+     * default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(PackageManagerMBeanImpl.class);
+
+    @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY)
+    private SlingRepository repository;
+
+    private static final String[] packageItemNames = {
+            "id",
+            "size",
+            "installed",
+            "installedDate",
+            "installedBy"
+    };
+    private static final String[] packageItemDescriptions = {
+            "Package Id",
+            "Package Size",
+            "Is installed",
+            "Install Date",
+            "Install User"
+    };
+
+    private static final OpenType[] packageItemTypes = {
+            SimpleType.STRING,
+            SimpleType.LONG,
+            SimpleType.BOOLEAN,
+            SimpleType.DATE,
+            SimpleType.STRING
+    };
+
+    private static final String[] packageIndexNames = { "id" };
+
+    private final CompositeType packageType;
+
+    private final TabularType packageTabularType;
+
+    public PackageManagerMBeanImpl() throws NotCompliantMBeanException, OpenDataException {
+        super(PackageManagerMBean.class);
+        packageType = new CompositeType("package", "Package Info", packageItemNames, packageItemDescriptions, packageItemTypes);
+        packageTabularType = new TabularType("packages", "List of Packages", packageType, packageIndexNames);
+    }
+
+    @Override
+    protected String getDescription(MBeanInfo info) {
+        return "Package Manager Information";
+    }
+
+    @Override
+    protected String getDescription(MBeanAttributeInfo info) {
+        if (info.getName().equals("Packages")) {
+            return "Available Packages";
+        }
+        return super.getDescription(info);
+    }
+
+    @Override
+    protected MBeanConstructorInfo[] getConstructors(MBeanConstructorInfo[] ctors, Object impl) {
+        return null;
+    }
+
+    public TabularData getPackages() {
+        TabularDataSupport packageData = new TabularDataSupport(packageTabularType);
+        if (repository != null) {
+            Session session = null;
+            try {
+                session = repository.loginAdministrative(null);
+                JcrPackageManager pkgMgr = new JcrPackageManagerImpl(session);
+                for (JcrPackage pkg: pkgMgr.listPackages()) {
+                    try {
+                        Object[] values = {
+                                pkg.getDefinition().getId().toString(),
+                                pkg.getSize(),
+                                pkg.isInstalled(),
+                                pkg.getDefinition().getLastUnpacked() == null ? null : pkg.getDefinition().getLastUnpacked().getTime(),
+                                pkg.getDefinition().getLastUnpackedBy()
+                        };
+                        packageData.put(new CompositeDataSupport(packageType, packageItemNames, values));
+                    } catch (Exception e) {
+                        log.warn("Can't add composite data", e);
+                    }
+                    pkg.close();
+                }
+            } catch (RepositoryException e) {
+                log.error("Repository error while retrieving package list", e);
+            } finally {
+                if (session != null) {
+                    session.logout();
+                }
+            }
+        } else {
+            log.warn("Unable to provide package list. Repository not bound.");
+        }
+        return packageData;
+	}
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackagingImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackagingImpl.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackagingImpl.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackagingImpl.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,80 @@
+/*
+ * 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 javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.jackrabbit.vault.packaging.JcrPackage;
+import org.apache.jackrabbit.vault.packaging.JcrPackageDefinition;
+import org.apache.jackrabbit.vault.packaging.JcrPackageManager;
+import org.apache.jackrabbit.vault.packaging.PackageManager;
+import org.apache.jackrabbit.vault.packaging.Packaging;
+import org.apache.jackrabbit.vault.util.JcrConstants;
+
+/**
+ * <code>PackagingImpl</code>...
+ */
+@Component(metatype = false, immediate = true)
+@Service(value = Packaging.class)
+public class PackagingImpl implements Packaging {
+
+    /**
+     * package manager is a singleton
+     */
+    private final PackageManager pkgManager = new PackageManagerImpl();
+
+    /**
+     * {@inheritDoc}
+     */
+    public PackageManager getPackageManager() {
+        return pkgManager;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JcrPackageManager getPackageManager(Session session) {
+        return new JcrPackageManagerImpl(session);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JcrPackageDefinition createPackageDefinition(Node defNode) {
+        return new JcrPackageDefinitionImpl(defNode);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JcrPackage open(Node node, boolean allowInvalid) throws RepositoryException {
+        JcrPackage pack = new JcrPackageImpl(node);
+        if (pack.isValid()) {
+            return pack;
+        } else if (allowInvalid
+                && node.isNodeType(JcrConstants.NT_HIERARCHYNODE)
+                && node.hasProperty(JcrConstants.JCR_CONTENT + "/" + JcrConstants.JCR_DATA)) {
+            return pack;
+        } else {
+            return null;
+        }
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/ZipVaultPackage.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/ZipVaultPackage.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/ZipVaultPackage.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/ZipVaultPackage.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,400 @@
+/*
+ * 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.util.Calendar;
+import java.util.List;
+import java.util.Properties;
+import java.util.regex.PatternSyntaxException;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.util.ISO8601;
+import org.apache.jackrabbit.vault.fs.config.MetaInf;
+import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
+import org.apache.jackrabbit.vault.fs.io.Importer;
+import org.apache.jackrabbit.vault.fs.io.ZipArchive;
+import org.apache.jackrabbit.vault.packaging.Dependency;
+import org.apache.jackrabbit.vault.packaging.InstallContext;
+import org.apache.jackrabbit.vault.packaging.PackageException;
+import org.apache.jackrabbit.vault.packaging.PackageId;
+import org.apache.jackrabbit.vault.packaging.VaultPackage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implements a vault package that is a zipped representation of a file vault
+ * export.
+ */
+public class ZipVaultPackage implements VaultPackage {
+
+    private static final Logger log = LoggerFactory.getLogger(ZipVaultPackage.class);
+
+    public static final String UNKNOWN_PATH = "/etc/packages/unknown";
+
+    private File file;
+
+    private Archive archive;
+
+    private boolean isTmpFile;
+
+    private PackageId id;
+
+    protected ZipVaultPackage(File file, boolean isTmpFile) throws IOException {
+        this(file, isTmpFile, false);
+    }
+
+    protected ZipVaultPackage(File file, boolean isTmpFile, boolean strict)
+            throws IOException {
+        this.file = file;
+        this.isTmpFile = isTmpFile;
+        if (strict) {
+            try {
+                archive = new ZipArchive(file);
+                archive.open(strict);
+            } catch (IOException e) {
+                log.error("Error while loading package {}.", file.getPath());
+                throw e;
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void close() {
+        if (archive != null) {
+            archive.close();
+            archive = null;
+        }
+        if (file != null && isTmpFile) {
+            FileUtils.deleteQuietly(file);
+        }
+        file = null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Archive getArchive() {
+        if (archive == null) {
+            if (file == null) {
+                log.error("Package already closed: " + id);
+                throw new IllegalStateException("Package already closed: " + id);
+            }
+            archive = new ZipArchive(file);
+            try {
+                archive.open(false);
+            } catch (IOException e) {
+                log.error("Archive not valid.", e);
+                throw new IllegalStateException("Archive not valid for file " + file, e);
+            }
+        }
+        return archive;
+    }
+
+    public PackageId getId() {
+        if (id == null) {
+            String version = getProperty(NAME_VERSION);
+            if (version == null) {
+                log.warn("Package does not specify a version. setting to ''");
+                 version = "";
+            }
+            String group = getProperty(NAME_GROUP);
+            String name = getProperty(NAME_NAME);
+            if (group != null && name != null) {
+                id = new PackageId(group, name, version);
+            } else {
+                // check for legacy packages that only contains a 'path' property
+                String path = getProperty("path");
+                if (path == null || path.length() == 0) {
+                    log.warn("Package does not specify a path. setting to 'unknown'");
+                    path = UNKNOWN_PATH;
+                }
+                id = new PackageId(path, version);
+            }
+        }
+        return id;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isValid() {
+        try {
+            return getMetaInf().getFilter() != null;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isClosed() {
+        return file == null;
+    }
+
+    /**
+     * Returns the file this package is based on.
+     * @return the file of this package or <code>null</code>.
+     */
+    public File getFile() {
+        return file;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public MetaInf getMetaInf() {
+        try {
+            return getArchive().getMetaInf();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getSize() {
+        return file == null
+                ? -1
+                : file.length();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Calendar getLastModified() {
+        return getDateProperty(NAME_LAST_MODIFIED);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getLastModifiedBy() {
+        return getProperty(NAME_LAST_MODIFIED_BY);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Calendar getCreated() {
+        return getDateProperty(NAME_CREATED);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getCreatedBy() {
+        return getProperty(NAME_CREATED_BY);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Calendar getLastWrapped() {
+        return getDateProperty(NAME_LAST_WRAPPED);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getLastWrappedBy() {
+        return getProperty(NAME_LAST_WRAPPED_BY);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getDescription() {
+        return getProperty(NAME_DESCRIPTION);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AccessControlHandling getACHandling() {
+        String ac = getProperty(NAME_AC_HANDLING);
+        if (ac == null) {
+            return AccessControlHandling.IGNORE;
+        } else {
+            try {
+                return AccessControlHandling.valueOf(ac.toUpperCase());
+            } catch (IllegalArgumentException e) {
+                log.warn("invalid access control handling configured: {}", ac);
+                return AccessControlHandling.IGNORE;
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean requiresRoot() {
+        return "true".equals(getProperty(NAME_REQUIRES_ROOT));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Dependency[] getDependencies() {
+        String deps = getProperty(NAME_DEPENDENCIES);
+        if (deps == null) {
+            return Dependency.EMPTY;
+        } else {
+            return Dependency.parse(deps);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void extract(Session session, ImportOptions opts) throws RepositoryException, PackageException {
+        extract(prepareExtract(session, opts), null);
+    }
+
+    /**
+     * Prepares extraction.
+     *
+     * @param session repository session
+     * @param opts import options
+     *
+     * @throws RepositoryException if a repository error during installation occurs.
+     * @throws PackageException if an error during packaging occurs
+     * @throws IllegalStateException if the package is not valid.
+     * @return installation context
+     */
+    protected InstallContextImpl prepareExtract(Session session, ImportOptions opts) throws RepositoryException, PackageException {
+        if (!isValid()) {
+            throw new IllegalStateException("Package not valid.");
+        }
+        // try to find any hooks
+        InstallHookProcessor hooks = new InstallHookProcessor();
+        if (!opts.isDryRun()) {
+            hooks.registerHooks(archive, opts.getHookClassLoader());
+        }
+
+        if (requiresRoot() || hooks.hasHooks()) {
+            if (!AdminPermissionChecker.hasAdministrativePermissions(session)) {
+                log.error("Package extraction requires admin session.");
+                throw new PackageException("Package extraction requires admin session (userid not allowed).");
+            }
+        }
+
+        Importer importer = new Importer(opts);
+        AccessControlHandling ac = getACHandling();
+        if (opts.getAccessControlHandling() == null) {
+            opts.setAccessControlHandling(ac);
+        }
+        String cndPattern = getProperty(NAME_CND_PATTERN);
+        if (cndPattern != null) {
+            try {
+                opts.setCndPattern(cndPattern);
+            } catch (PatternSyntaxException e) {
+                throw new PackageException("Specified CND pattern not valid.", e);
+            }
+        }
+
+        return new InstallContextImpl(session.getRootNode(), this, importer, hooks);
+    }
+
+    /**
+     * Same as above but the given subPackages argument receives a list of
+     * potential sub packages.
+     *
+     * @param ctx install context
+     * @param subPackages receives the list of potential sub packages
+     *
+     * @throws RepositoryException if a repository error during installation occurs.
+     * @throws PackageException if an error during packaging occurs
+     * @throws IllegalStateException if the package is not valid.
+     */
+    protected void extract(InstallContextImpl ctx,
+                           List<String> subPackages)
+            throws RepositoryException, PackageException {
+        log.info("Extracting {}", getId());
+        InstallHookProcessor hooks = ctx.getHooks();
+        Importer importer = ctx.getImporter();
+        try {
+            if (!hooks.execute(ctx)) {
+                throw new PackageException("Import aborted during prepare phase.");
+            }
+            try {
+                importer.run(archive, ctx.getImportRoot());
+            } catch (Exception e) {
+                log.error("Error during install.", e);
+                ctx.setPhase(InstallContext.Phase.INSTALL_FAILED);
+                hooks.execute(ctx);
+                throw new PackageException(e);
+            }
+            ctx.setPhase(InstallContext.Phase.INSTALLED);
+            hooks.execute(ctx);
+            if (importer.hasErrors() && ctx.getOptions().isStrict()) {
+                ctx.setPhase(InstallContext.Phase.INSTALL_FAILED);
+                hooks.execute(ctx);
+                throw new PackageException("Errors during import.");
+            }
+        } finally {
+            ctx.setPhase(InstallContext.Phase.END);
+            hooks.execute(ctx);
+        }
+        if (subPackages != null) {
+            subPackages.addAll(importer.getSubPackages());
+        }
+        log.info("Extracting {} completed.", getId());
+    }
+
+    private Calendar getDateProperty(String name) {
+        try {
+            String p = getProperty(name);
+            return p == null
+                    ? null
+                    : ISO8601.parse(p);
+        } catch (Exception e) {
+            log.error("Error while converting date property", e);
+            return null;
+        }
+    }
+
+    private String getProperty(String name) {
+        try {
+            Properties props = getMetaInf().getProperties();
+            return props == null ? null : props.getProperty(name);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } catch (Throwable e) {
+            // ignore
+        }
+        super.finalize();
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/BinaryCheckOutputStream.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/BinaryCheckOutputStream.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/BinaryCheckOutputStream.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/BinaryCheckOutputStream.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,81 @@
+/*
+ * 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.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * <code>BinaryCheckOutputStream</code>...
+ */
+public class BinaryCheckOutputStream extends OutputStream {
+
+    private final OutputStream out;
+
+    private boolean binary;
+
+    private static final boolean[] binaries = new boolean[256];
+    static {
+        for (int i=0; i<32; i++) {
+            binaries[i] = true;
+        }
+        binaries['\r'] = false;
+        binaries['\n'] = false;
+        binaries['\t'] = false;
+        binaries['\b'] = false;
+        binaries['\f'] = false;
+    }
+
+    public BinaryCheckOutputStream(OutputStream out) {
+        this.out = out;
+    }
+
+    public boolean isBinary() {
+        return binary;
+    }
+
+    public void write(int b) throws IOException {
+        if (!binary) {
+            binary = binaries[b & 0xff];
+        }
+        out.write(b);
+    }
+
+    public void write(byte[] b) throws IOException {
+        for (int i=0; i < b.length && !binary; i++) {
+            binary = binaries[b[i] & 0xff];
+        }
+        out.write(b);
+    }
+
+    public void write(byte[] b, int off, int len) throws IOException {
+        for (int i=0; i < len && !binary; i++) {
+            binary = binaries[b[i+off] & 0xff];
+        }
+        out.write(b, off, len);
+    }
+
+    public void flush() throws IOException {
+        out.flush();
+    }
+
+    public void close() throws IOException {
+        out.close();
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/Constants.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/Constants.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/Constants.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/Constants.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,123 @@
+/*
+ * 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.util;
+
+/**
+ * Defines some (file) name constants.
+ *
+ */
+public interface Constants {
+
+    /**
+     * the namespace uri of the 'vlt' namespace
+     */
+    String VAULT_NS_URI = "http://www.day.com/jcr/vault/1.0";
+
+    /**
+     * name of the {@value} directory
+     */
+    String META_INF = "META-INF";
+
+    /**
+     * name of the {@value} directory inside the {@value #META_INF}.
+     */
+    String VAULT_DIR = "vault";
+
+    /**
+     * default encoding for strings
+     */
+    String ENCODING = "utf-8";
+
+    /**
+     * path of the meta directory
+     */
+    String META_DIR = META_INF + "/" + VAULT_DIR;
+
+    /**
+     * name of the "hooks" directory
+     */
+    String HOOKS_DIR = "hooks";
+
+    /**
+     * name of the root directory under which all content goes.
+     */
+    String ROOT_DIR = "jcr_root";
+
+    /**
+     * name of the vault fs config file.
+     */
+    String CONFIG_XML = "config.xml";
+
+    /**
+     * name of the filter file.
+     */
+    String FILTER_XML = "filter.xml";
+
+    /**
+     * name of the filter file.
+     */
+    String FILTER_VLT_XML = "filter-vlt.xml";
+
+    /**
+     * name of the global settings file.
+     */
+    String SETTINGS_XML = "settings.xml";
+
+    /**
+     * name of the package definition directory
+     */
+    String PACKAGE_DEFINITION_XML = "definition/.content.xml";
+    
+    /**
+     * name of the auth config
+     */
+    String AUTH_XML = "auth.xml";
+
+    /**
+     * name of the node types file.
+     */
+    String NODETYPES_CND = "nodetypes.cnd";
+
+    /**
+     * name of the custom export properties file
+     */
+    String PROPERTIES_XML = "properties.xml";
+
+    /**
+     * name of the custom privileges file
+     * @since 3.0
+     */
+    String PRIVILEGES_XML = "privileges.xml";
+
+    /**
+     * name of the file for generic serializations
+     */
+    String DOT_CONTENT_XML = ".content.xml";
+
+    /**
+     * empty string array
+     */
+    String[] EMPTY_STRING_ARRAY = new String[0];
+
+    /**
+     * the native file separator char
+     */
+    String FS_NATIVE = System.getProperty("file.separator");
+
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DefaultProgressListener.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DefaultProgressListener.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DefaultProgressListener.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DefaultProgressListener.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,57 @@
+/*
+ * 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.util;
+
+import java.io.PrintWriter;
+
+import org.apache.jackrabbit.vault.fs.api.ProgressTrackerListener;
+
+/**
+ * <code>DefaultProgressListener</code>...
+*/
+public class DefaultProgressListener implements ProgressTrackerListener {
+
+    private final PrintWriter out;
+
+    public DefaultProgressListener() {
+        this(new PrintWriter(System.out));
+    }
+
+    public DefaultProgressListener(PrintWriter out) {
+        this.out = out;
+    }
+
+    public void onMessage(Mode mode, String action, String path) {
+        String name = path;
+        if (mode == Mode.PATHS) {
+            name = path.substring(path.lastIndexOf('/') + 1);
+        }
+        out.printf("%s %s%n", action, name);
+        out.flush();
+    }
+
+    public void onError(Mode mode, String path, Exception e) {
+        String name = path;
+        if (mode == Mode.PATHS) {
+            name = path.substring(path.lastIndexOf('/') + 1);
+        }
+        out.printf("E %s (%s)%n", name, e.toString());
+        out.flush();
+    }
+
+}
\ No newline at end of file