org.exist.repo.Deployment.java Source code

Java tutorial

Introduction

Here is the source code for org.exist.repo.Deployment.java

Source

/*
 *  eXist Open Source Native XML Database
 *  Copyright (C) 2001-12 The eXist Project
 *  http://exist-db.org
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  $Id$
 */
package org.exist.repo;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.log4j.Logger;
import org.exist.EXistException;
import org.exist.collections.Collection;
import org.exist.collections.IndexInfo;
import org.exist.config.ConfigurationException;
import org.exist.dom.BinaryDocument;
import org.exist.dom.QName;
import org.exist.memtree.*;
import org.exist.security.Permission;
import org.exist.security.PermissionDeniedException;
import org.exist.security.internal.aider.GroupAider;
import org.exist.security.internal.aider.UserAider;
import org.exist.security.xacml.AccessContext;
import org.exist.source.FileSource;
import org.exist.storage.DBBroker;
import org.exist.storage.lock.Lock;
import org.exist.storage.txn.TransactionManager;
import org.exist.storage.txn.Txn;
import org.exist.util.LockException;
import org.exist.util.MimeTable;
import org.exist.util.MimeType;
import org.exist.util.SyntaxException;
import org.exist.util.serializer.AttrList;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.*;
import org.exist.xquery.util.DocUtils;
import org.exist.xquery.value.DateTimeValue;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceIterator;
import org.exist.xquery.value.Type;
import org.expath.pkg.repo.*;
import org.expath.pkg.repo.Package;
import org.expath.pkg.repo.deps.DependencyVersion;
import org.expath.pkg.repo.tui.BatchUserInteraction;
import org.w3c.dom.Element;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import java.io.*;
import java.util.Date;
import java.util.Stack;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * Deploy a .xar package into the database using the information provided
 * in expath-pkg.xml and repo.xml.
 */
public class Deployment {

    public final static String PROPERTY_APP_ROOT = "repo.root-collection";

    private final static Logger LOG = Logger.getLogger(Deployment.class);

    private final static String REPO_NAMESPACE = "http://exist-db.org/xquery/repo";
    private final static String PKG_NAMESPACE = "http://expath.org/ns/pkg";

    private final static QName SETUP_ELEMENT = new QName("setup", REPO_NAMESPACE);
    private static final QName PRE_SETUP_ELEMENT = new QName("prepare", REPO_NAMESPACE);
    private static final QName POST_SETUP_ELEMENT = new QName("finish", REPO_NAMESPACE);
    private static final QName TARGET_COLL_ELEMENT = new QName("target", REPO_NAMESPACE);
    private static final QName PERMISSIONS_ELEMENT = new QName("permissions", REPO_NAMESPACE);
    private static final QName CLEANUP_ELEMENT = new QName("cleanup", REPO_NAMESPACE);
    private static final QName DEPLOYED_ELEMENT = new QName("deployed", REPO_NAMESPACE);
    private static final QName DEPENDENCY_ELEMENT = new QName("dependency", PKG_NAMESPACE);

    private DBBroker broker;

    private String user = null;
    private String password = null;
    private String group = null;
    private int perms = -1;
    private String permsStr = null;

    public Deployment(DBBroker broker) {
        this.broker = broker;
    }

    protected File getPackageDir(String pkgName, ExistRepository repo) throws PackageException {
        File packageDir = null;

        for (final Packages pp : repo.getParentRepo().listPackages()) {
            final org.expath.pkg.repo.Package pkg = pp.latest();
            if (pkg.getName().equals(pkgName)) {
                packageDir = getPackageDir(pkg);
            }
        }
        return packageDir;
    }

    protected File getPackageDir(Package pkg) {
        final FileSystemStorage.FileSystemResolver resolver = (FileSystemStorage.FileSystemResolver) pkg
                .getResolver();
        return resolver.resolveResourceAsFile(".");
    }

    protected org.expath.pkg.repo.Package getPackage(String pkgName, ExistRepository repo) throws PackageException {
        for (final Packages pp : repo.getParentRepo().listPackages()) {
            final org.expath.pkg.repo.Package pkg = pp.latest();
            if (pkg.getName().equals(pkgName)) {
                return pkg;
            }
        }
        return null;
    }

    protected DocumentImpl getRepoXML(File packageDir) throws PackageException {
        // find and parse the repo.xml descriptor
        final File repoFile = new File(packageDir, "repo.xml");
        if (!repoFile.canRead()) {
            return null;
        }
        try {
            return DocUtils.parse(broker.getBrokerPool(), null, new FileInputStream(repoFile));
        } catch (final XPathException e) {
            throw new PackageException("Failed to parse repo.xml: " + e.getMessage(), e);
        } catch (final FileNotFoundException e) {
            throw new PackageException("Failed to read repo.xml: " + e.getMessage(), e);
        }
    }

    public String installAndDeploy(File xar, PackageLoader loader) throws PackageException, IOException {
        return installAndDeploy(xar, loader, true);
    }

    /**
     * Install and deploy a give xar archive. Dependencies are installed from
     * the PackageLoader.
     *
     * @param xar the .xar file to install
     * @param loader package loader to use
     * @param enforceDeps when set to true, the method will throw an exception if a dependency could not be resolved
     *                    or an older version of the required dependency is installed and needs to be replaced.
     */
    public String installAndDeploy(File xar, PackageLoader loader, boolean enforceDeps)
            throws PackageException, IOException {
        final DocumentImpl document = getDescriptor(xar);
        final ElementImpl root = (ElementImpl) document.getDocumentElement();
        final String name = root.getAttribute("name");
        final String pkgVersion = root.getAttribute("version");

        final ExistRepository repo = broker.getBrokerPool().getExpathRepo();
        final Packages packages = repo.getParentRepo().getPackages(name);
        if (packages != null && (!enforceDeps || pkgVersion.equals(packages.latest().getVersion()))) {
            LOG.info("Application package " + name + " already installed. Skipping.");
            return null;
        }

        InMemoryNodeSet deps;
        try {
            deps = findElements(root, DEPENDENCY_ELEMENT);
            for (final SequenceIterator i = deps.iterate(); i.hasNext();) {
                final Element dependency = (Element) i.nextItem();
                final String pkgName = dependency.getAttribute("package");
                final String versionStr = dependency.getAttribute("version");
                final String semVer = dependency.getAttribute("semver");
                final String semVerMin = dependency.getAttribute("semver-min");
                final String semVerMax = dependency.getAttribute("semver-max");
                PackageLoader.Version version = null;
                if (semVer != null) {
                    version = new PackageLoader.Version(semVer, true);
                } else if (semVerMax != null || semVerMin != null) {
                    version = new PackageLoader.Version(semVerMin, semVerMax);
                } else if (pkgVersion != null) {
                    version = new PackageLoader.Version(versionStr, false);
                }
                if (pkgName != null) {
                    LOG.info("Package " + name + " depends on " + pkgName);
                    boolean isInstalled = false;
                    if (repo.getParentRepo().getPackages(pkgName) != null) {
                        LOG.debug("Package " + pkgName + " already installed");
                        Packages pkgs = repo.getParentRepo().getPackages(pkgName);
                        // check if installed package matches required version
                        if (pkgs != null) {
                            if (version != null) {
                                Package latest = pkgs.latest();
                                DependencyVersion depVersion = version.getDependencyVersion();
                                if (depVersion.isCompatible(latest.getVersion())) {
                                    isInstalled = true;
                                } else {
                                    LOG.debug("Package " + pkgName + " needs to be upgraded");
                                    if (enforceDeps) {
                                        throw new PackageException("Package requires version " + version.toString()
                                                + " of package " + pkgName + ". Installed version is "
                                                + latest.getVersion() + ". Please upgrade!");
                                    }
                                }
                            } else {
                                isInstalled = true;
                            }
                            if (isInstalled) {
                                LOG.debug("Package " + pkgName + " already installed");
                            }
                        }
                    }
                    if (!isInstalled && loader != null) {
                        final File depFile = loader.load(pkgName, version);
                        if (depFile != null) {
                            installAndDeploy(depFile, loader);
                        } else {
                            if (enforceDeps) {
                                LOG.warn("Missing dependency: package " + pkgName
                                        + " could not be resolved. This error "
                                        + "is not fatal, but the package may not work as expected");
                            } else {
                                throw new PackageException(
                                        "Missing dependency: package " + pkgName + " could not be resolved.");
                            }
                        }
                    }
                }
            }
        } catch (final XPathException e) {
            throw new PackageException("Invalid descriptor found in " + xar.getAbsolutePath());
        }

        // installing the xar into the expath repo
        LOG.info("Installing package " + xar.getAbsolutePath());
        final UserInteractionStrategy interact = new BatchUserInteraction();
        final org.expath.pkg.repo.Package pkg = repo.getParentRepo().installPackage(xar, true, interact);
        final ExistPkgInfo info = (ExistPkgInfo) pkg.getInfo("exist");
        if (info != null && !info.getJars().isEmpty()) {
            ClasspathHelper.updateClasspath(broker.getBrokerPool(), pkg);
        }
        broker.getBrokerPool().getXQueryPool().clear();
        final String pkgName = pkg.getName();
        // signal status
        broker.getBrokerPool().reportStatus("Installing app: " + pkg.getAbbrev());
        repo.reportAction(ExistRepository.Action.INSTALL, pkg.getName());

        LOG.info("Deploying package " + pkgName);
        return deploy(pkgName, repo, null);
    }

    public String undeploy(String pkgName, ExistRepository repo) throws PackageException {
        final File packageDir = getPackageDir(pkgName, repo);
        if (packageDir == null)
        // fails silently if package dir is not found?
        {
            return null;
        }
        final DocumentImpl repoXML = getRepoXML(packageDir);
        final Package pkg = getPackage(pkgName, repo);
        if (repoXML != null) {
            ElementImpl target = null;
            try {
                target = findElement(repoXML, TARGET_COLL_ELEMENT);
                final ElementImpl cleanup = findElement(repoXML, CLEANUP_ELEMENT);
                if (cleanup != null) {
                    runQuery(null, packageDir, cleanup.getStringValue(), false);
                }

                uninstall(pkg, target);
                return target == null ? null : target.getStringValue();
            } catch (final XPathException e) {
                throw new PackageException("Error found while processing repo.xml: " + e.getMessage(), e);
            } catch (final IOException e) {
                throw new PackageException("Error found while processing repo.xml: " + e.getMessage(), e);
            }
        } else {
            // we still may need to remove the copy of the package from /db/system/repo
            uninstall(pkg, null);
        }
        return null;
    }

    public String deploy(String pkgName, ExistRepository repo, String userTarget)
            throws PackageException, IOException {
        final File packageDir = getPackageDir(pkgName, repo);
        if (packageDir == null) {
            throw new PackageException("Package not found: " + pkgName);
        }
        final DocumentImpl repoXML = getRepoXML(packageDir);
        if (repoXML == null) {
            return null;
        }
        try {
            // if there's a <setup> element, run the query it points to
            final ElementImpl setup = findElement(repoXML, SETUP_ELEMENT);
            String path = setup == null ? null : setup.getStringValue();
            if (path != null && path.length() > 0) {
                runQuery(null, packageDir, path, true);
                return null;
            } else {
                // otherwise copy all child directories to the target collection
                XmldbURI targetCollection = null;
                if (userTarget != null) {
                    try {
                        targetCollection = XmldbURI.create(userTarget);
                    } catch (final Exception e) {
                        throw new PackageException("Bad collection URI: " + userTarget, e);
                    }
                } else {
                    final ElementImpl target = findElement(repoXML, TARGET_COLL_ELEMENT);
                    if (target != null) {
                        final String targetPath = target.getStringValue();
                        if (targetPath.length() > 0) {
                            // determine target collection
                            try {
                                targetCollection = XmldbURI.create(getTargetCollection(targetPath));
                            } catch (final Exception e) {
                                throw new PackageException(
                                        "Bad collection URI for <target> element: " + target.getStringValue(), e);
                            }
                        }
                    }
                }
                if (targetCollection == null) {
                    // no target means: package does not need to be deployed into database
                    // however, we need to preserve a copy for backup purposes
                    final Package pkg = getPackage(pkgName, repo);
                    final String pkgColl = pkg.getAbbrev() + "-" + pkg.getVersion();
                    targetCollection = XmldbURI.SYSTEM.append("repo/" + pkgColl);
                }
                final ElementImpl permissions = findElement(repoXML, PERMISSIONS_ELEMENT);
                if (permissions != null) {
                    // get user, group and default permissions
                    user = permissions.getAttribute("user");
                    group = permissions.getAttribute("group");
                    password = permissions.getAttribute("password");
                    String mode = permissions.getAttribute("mode");
                    try {
                        perms = Integer.parseInt(mode, 8);
                    } catch (final NumberFormatException e) {
                        permsStr = mode;
                        if (!permsStr.matches("^[rwx-]{9}")) {
                            throw new PackageException("Bad format for mode attribute in <permissions>: " + mode);
                        }
                    }
                }

                // run the pre-setup query if present
                final ElementImpl preSetup = findElement(repoXML, PRE_SETUP_ELEMENT);
                if (preSetup != null) {
                    path = preSetup.getStringValue();
                    if (path.length() > 0) {
                        runQuery(targetCollection, packageDir, path, true);
                    }
                }

                // any required users and group should have been created by the pre-setup query.
                // check for invalid users now.
                checkUserSettings();

                // install
                scanDirectory(packageDir, targetCollection, true);

                // run the post-setup query if present
                final ElementImpl postSetup = findElement(repoXML, POST_SETUP_ELEMENT);
                if (postSetup != null) {
                    path = postSetup.getStringValue();
                    if (path.length() > 0) {
                        runQuery(targetCollection, packageDir, path, false);
                    }
                }

                storeRepoXML(repoXML, targetCollection);

                // TODO: it should be save to clean up the file system after a package
                // has been deployed. Might be enabled after 2.0
                //cleanup(pkgName, repo);

                return targetCollection.getCollectionPath();
            }
        } catch (final XPathException e) {
            throw new PackageException("Error found while processing repo.xml: " + e.getMessage(), e);
        }
    }

    /**
     * After deployment, clean up the package directory and remove all files which have been
     * stored into the db. They are not needed anymore. Only preserve the descriptors and the
     * contents directory.
     *
     * @param pkgName
     * @param repo
     * @throws PackageException
     */
    private void cleanup(String pkgName, ExistRepository repo) throws PackageException {
        final Package pkg = getPackage(pkgName, repo);
        final String abbrev = pkg.getAbbrev();
        final File packageDir = getPackageDir(pkg);
        if (packageDir == null) {
            throw new PackageException("Cleanup: package dir for package " + pkgName + " not found");
        }
        File[] filesToDelete = packageDir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                String name = file.getName();
                if (file.isDirectory()) {
                    return !(name.equals(abbrev) || name.equals("content"));
                } else {
                    return !(name.equals("expath-pkg.xml") || name.equals("repo.xml") || "exist.xml".equals(name)
                            || name.startsWith("icon"));
                }
            }
        });
        for (final File fileToDelete : filesToDelete) {
            try {
                FileUtils.forceDelete(fileToDelete);
            } catch (final IOException e) {
                LOG.warn("Cleanup: failed to delete file " + fileToDelete.getAbsolutePath() + " in package "
                        + pkgName);
            }
        }
    }

    private String getTargetCollection(String targetFromRepo) {
        final String appRoot = (String) broker.getConfiguration().getProperty(PROPERTY_APP_ROOT);
        if (appRoot != null) {
            if (targetFromRepo.startsWith("/db/")) {
                targetFromRepo = targetFromRepo.substring(4);
            }
            return appRoot + targetFromRepo;
        }
        if (targetFromRepo.startsWith("/db")) {
            return targetFromRepo;
        } else {
            return "/db/" + targetFromRepo;
        }
    }

    /**
     * Delete the target collection of the package. If there's no repo.xml descriptor,
     * target will be null.
     *
     * @param pkg
     * @param target
     * @throws PackageException
     */
    private void uninstall(Package pkg, ElementImpl target) throws PackageException {
        // determine target collection
        XmldbURI targetCollection;
        if (target == null || target.getStringValue().length() == 0) {
            final String pkgColl = pkg.getAbbrev() + "-" + pkg.getVersion();
            targetCollection = XmldbURI.SYSTEM.append("repo/" + pkgColl);
        } else {
            final String targetPath = target.getStringValue();
            try {
                targetCollection = XmldbURI.create(getTargetCollection(targetPath));
            } catch (final Exception e) {
                throw new PackageException("Bad collection URI for <target> element: " + targetPath);
            }
        }
        final TransactionManager mgr = broker.getBrokerPool().getTransactionManager();
        final Txn txn = mgr.beginTransaction();
        try {
            Collection collection = broker.getOrCreateCollection(txn, targetCollection);
            if (collection != null) {
                broker.removeCollection(txn, collection);
            }
            if (target != null) {
                final XmldbURI configCollection = XmldbURI.CONFIG_COLLECTION_URI.append(targetCollection);
                collection = broker.getOrCreateCollection(txn, configCollection);
                if (collection != null) {
                    broker.removeCollection(txn, collection);
                }
            }
            mgr.commit(txn);
        } catch (final Exception e) {
            LOG.error("Exception occurred while removing package.", e);
            mgr.abort(txn);
        } finally {
            mgr.close(txn);
        }
    }

    /**
     * Store repo.xml into the db. Adds the time of deployment to the descriptor.
     *
     * @param repoXML
     * @param targetCollection
     * @throws XPathException
     */
    private void storeRepoXML(DocumentImpl repoXML, XmldbURI targetCollection)
            throws PackageException, XPathException {
        // Store repo.xml
        final DateTimeValue time = new DateTimeValue(new Date());
        final MemTreeBuilder builder = new MemTreeBuilder();
        builder.startDocument();
        final UpdatingDocumentReceiver receiver = new UpdatingDocumentReceiver(builder, time.getStringValue());
        try {
            repoXML.copyTo(broker, receiver);
        } catch (final SAXException e) {
            throw new PackageException("Error while updating repo.xml: " + e.getMessage());
        }
        builder.endDocument();
        final DocumentImpl updatedXML = builder.getDocument();

        final TransactionManager mgr = broker.getBrokerPool().getTransactionManager();
        final Txn txn = mgr.beginTransaction();
        try {
            final Collection collection = broker.getOrCreateCollection(txn, targetCollection);
            final XmldbURI name = XmldbURI.createInternal("repo.xml");
            final IndexInfo info = collection.validateXMLResource(txn, broker, name, updatedXML);
            final Permission permission = info.getDocument().getPermissions();
            setPermissions(false, MimeType.XML_TYPE, permission);

            collection.store(txn, broker, info, updatedXML, false);

            mgr.commit(txn);
        } catch (final Exception e) {
            mgr.abort(txn);
        } finally {
            mgr.close(txn);
        }
    }

    private void checkUserSettings() throws PackageException {
        final org.exist.security.SecurityManager secman = broker.getBrokerPool().getSecurityManager();
        try {
            if (group != null && !secman.hasGroup(group)) {
                final GroupAider aider = new GroupAider(group);
                secman.addGroup(aider);
            }
            if (user != null && !secman.hasAccount(user)) {
                final UserAider aider = new UserAider(user);
                aider.setPassword(password);
                if (group != null) {
                    aider.addGroup(group);
                }

                secman.addAccount(aider);
            }
        } catch (final ConfigurationException e) {
            throw new PackageException("Failed to create user: " + user, e);
        } catch (final PermissionDeniedException e) {
            throw new PackageException("Failed to create user: " + user, e);
        } catch (final EXistException e) {
            throw new PackageException("Failed to create user: " + user, e);
        }
    }

    private Sequence runQuery(XmldbURI targetCollection, File tempDir, String fileName, boolean preInstall)
            throws PackageException, IOException, XPathException {
        final File xquery = new File(tempDir, fileName);
        if (!xquery.canRead()) {
            LOG.warn("The XQuery resource specified in the <setup> element was not found");
            return Sequence.EMPTY_SEQUENCE;
        }
        final XQuery xqs = broker.getXQueryService();
        final XQueryContext ctx = xqs.newContext(AccessContext.REST);
        ctx.declareVariable("dir", tempDir.getAbsolutePath());
        final File home = broker.getConfiguration().getExistHome();
        ctx.declareVariable("home", home.getAbsolutePath());
        if (targetCollection != null) {
            ctx.declareVariable("target", targetCollection.toString());
            ctx.setModuleLoadPath(XmldbURI.EMBEDDED_SERVER_URI + targetCollection.toString());
        } else {
            ctx.declareVariable("target", Sequence.EMPTY_SEQUENCE);
        }
        if (preInstall)
        // when running pre-setup scripts, base path should point to directory
        // because the target collection does not yet exist
        {
            ctx.setModuleLoadPath(tempDir.getAbsolutePath());
        }

        CompiledXQuery compiled;
        try {
            compiled = xqs.compile(ctx, new FileSource(xquery, "UTF-8", false));
            return xqs.execute(compiled, null);
        } catch (final PermissionDeniedException e) {
            throw new PackageException(e.getMessage(), e);
        }
    }

    /**
     * Scan a directory and import all files and sub directories into the target
     * collection.
     *
     * @param directory
     * @param target
     */
    private void scanDirectory(File directory, XmldbURI target, boolean inRootDir) {
        final TransactionManager mgr = broker.getBrokerPool().getTransactionManager();
        final Txn txn = mgr.beginTransaction();
        Collection collection = null;
        try {
            collection = broker.getOrCreateCollection(txn, target);
            setPermissions(true, null, collection.getPermissionsNoLock());
            broker.saveCollection(txn, collection);
            mgr.commit(txn);
        } catch (final Exception e) {
            mgr.abort(txn);
        } finally {
            mgr.close(txn);
        }

        try {
            // lock the collection while we store the files
            // TODO: could be released after each operation
            collection.getLock().acquire(Lock.WRITE_LOCK);
            storeFiles(directory, collection, inRootDir);
        } catch (final LockException e) {
            e.printStackTrace();
        } finally {
            collection.getLock().release(Lock.WRITE_LOCK);
        }

        // scan sub directories
        final File[] files = directory.listFiles();
        for (final File file : files) {
            if (file.isDirectory()) {
                scanDirectory(file, target.append(file.getName()), false);
            }
        }
    }

    /**
     * Import all files in the given directory into the target collection
     *
     * @param directory
     * @param targetCollection
     */
    private void storeFiles(File directory, Collection targetCollection, boolean inRootDir) {
        final File[] files = directory.listFiles();
        final MimeTable mimeTab = MimeTable.getInstance();
        final TransactionManager mgr = broker.getBrokerPool().getTransactionManager();
        for (final File file : files) {
            if (inRootDir && "repo.xml".equals(file.getName())) {
                continue;
            }
            if (!file.isDirectory()) {
                MimeType mime = mimeTab.getContentTypeFor(file.getName());
                if (mime == null) {
                    mime = MimeType.BINARY_TYPE;
                }
                final XmldbURI name = XmldbURI.create(file.getName());

                final Txn txn = mgr.beginTransaction();
                try {
                    if (mime.isXMLType()) {
                        final InputSource is = new InputSource(file.toURI().toASCIIString());
                        final IndexInfo info = targetCollection.validateXMLResource(txn, broker, name, is);
                        info.getDocument().getMetadata().setMimeType(mime.getName());
                        final Permission permission = info.getDocument().getPermissions();
                        setPermissions(false, mime, permission);

                        targetCollection.store(txn, broker, info, is, false);
                    } else {
                        final long size = file.length();
                        final FileInputStream is = new FileInputStream(file);
                        final BinaryDocument doc = targetCollection.addBinaryResource(txn, broker, name, is,
                                mime.getName(), size);
                        is.close();

                        final Permission permission = doc.getPermissions();
                        setPermissions(false, mime, permission);
                        doc.getMetadata().setMimeType(mime.getName());
                        broker.storeXMLResource(txn, doc);
                    }
                    mgr.commit(txn);
                } catch (final Exception e) {
                    mgr.abort(txn);
                    e.printStackTrace();
                } finally {
                    mgr.close(txn);
                }
            }
        }
    }

    /**
     * Set owner, group and permissions. For XQuery resources, always set the executable flag.
     * @param mime
     * @param permission
     */
    private void setPermissions(boolean isCollection, MimeType mime, Permission permission)
            throws PermissionDeniedException {
        if (user != null) {
            permission.setOwner(user);
        }
        if (group != null) {
            permission.setGroup(group);
        }

        int mode;
        if (permsStr != null) {
            try {
                permission.setMode(permsStr);
                mode = permission.getMode();
            } catch (final SyntaxException e) {
                LOG.warn("Incorrect permissions string: " + permsStr + ". Falling back to default.");
                mode = permission.getMode();
            }
        } else if (perms > -1) {
            mode = perms;
        } else {
            mode = permission.getMode();
        }

        if (isCollection || (mime != null && mime.getName().equals(MimeType.XQUERY_TYPE.getName()))) {
            mode = mode | 0111;
        }
        permission.setMode(mode);
    }

    private ElementImpl findElement(NodeImpl root, QName qname) throws XPathException {
        final InMemoryNodeSet setupNodes = new InMemoryNodeSet();
        root.selectDescendants(false, new NameTest(Type.ELEMENT, qname), setupNodes);
        if (setupNodes.getItemCount() == 0) {
            return null;
        }
        return (ElementImpl) setupNodes.itemAt(0);
    }

    private InMemoryNodeSet findElements(NodeImpl root, QName qname) throws XPathException {
        final InMemoryNodeSet setupNodes = new InMemoryNodeSet();
        root.selectDescendants(false, new NameTest(Type.ELEMENT, qname), setupNodes);
        return setupNodes;
    }

    public String getNameFromDescriptor(File xar) throws IOException, PackageException {
        final DocumentImpl doc = getDescriptor(xar);
        final Element root = doc.getDocumentElement();
        return root.getAttribute("name");
    }

    public DocumentImpl getDescriptor(File jar) throws IOException, PackageException {
        final InputStream istream = new BufferedInputStream(new FileInputStream(jar));
        final JarInputStream jis = new JarInputStream(istream);
        JarEntry entry;
        DocumentImpl doc = null;
        while ((entry = jis.getNextJarEntry()) != null) {
            if (!entry.isDirectory() && "expath-pkg.xml".equals(entry.getName())) {
                final ByteArrayOutputStream bos = new ByteArrayOutputStream();
                int c;
                final byte[] b = new byte[4096];
                while ((c = jis.read(b)) > 0) {
                    bos.write(b, 0, c);
                }

                bos.close();

                final byte[] data = bos.toByteArray();

                final ByteArrayInputStream bis = new ByteArrayInputStream(data);
                try {
                    doc = DocUtils.parse(broker.getBrokerPool(), null, bis);
                } catch (final XPathException e) {
                    throw new PackageException("Error while parsing expath-pkg.xml: " + e.getMessage(), e);
                }
                break;
            }
        }
        jis.close();
        return doc;
    }

    /**
     * Update repo.xml while copying it. For security reasons, make sure
     * any default password is removed before uploading.
     */
    private class UpdatingDocumentReceiver extends DocumentBuilderReceiver {

        private String time;
        private Stack<String> stack = new Stack<String>();

        public UpdatingDocumentReceiver(MemTreeBuilder builder, String time) {
            super(builder, false);
            this.time = time;
        }

        @Override
        public void startElement(QName qname, AttrList attribs) {
            stack.push(qname.getLocalName());
            AttrList newAttrs = attribs;
            if (attribs != null && "permissions".equals(qname.getLocalName())) {
                newAttrs = new AttrList();
                for (int i = 0; i < attribs.getLength(); i++) {
                    if (!"password".equals(attribs.getQName(i).getLocalName())) {
                        newAttrs.addAttribute(attribs.getQName(i), attribs.getValue(i), attribs.getType(i));
                    }
                }
            }

            if (!"deployed".equals(qname.getLocalName())) {
                super.startElement(qname, newAttrs);
            }
        }

        @Override
        public void startElement(String namespaceURI, String localName, String qName, Attributes attrs)
                throws SAXException {
            stack.push(localName);
            if (!"deployed".equals(localName)) {
                super.startElement(namespaceURI, localName, qName, attrs);
            }
        }

        @Override
        public void endElement(QName qname) throws SAXException {
            stack.pop();
            if ("meta".equals(qname.getLocalName())) {
                addDeployTime();
            }
            if (!"deployed".equals(qname.getLocalName())) {
                super.endElement(qname);
            }
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            stack.pop();
            if ("meta".equals(localName)) {
                addDeployTime();
            }
            if (!"deployed".equals(localName)) {
                super.endElement(uri, localName, qName);
            }
        }

        @Override
        public void attribute(QName qname, String value) throws SAXException {
            final String current = stack.peek();
            if (!("permissions".equals(current) && "password".equals(qname.getLocalName()))) {
                super.attribute(qname, value);
            }
        }

        @Override
        public void characters(char[] ch, int start, int len) throws SAXException {
            final String current = stack.peek();
            if (!"deployed".equals(current)) {
                super.characters(ch, start, len);
            }
        }

        @Override
        public void characters(CharSequence seq) throws SAXException {
            final String current = stack.peek();
            if (!"deployed".equals(current)) {
                super.characters(seq);
            }
        }

        private void addDeployTime() throws SAXException {
            super.startElement(DEPLOYED_ELEMENT, null);
            super.characters(time);
            super.endElement(DEPLOYED_ELEMENT);
        }
    }
}