/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-2017 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
*/
package org.exist.xquery.modules.expathrepo;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.dom.persistent.BinaryDocument;
import org.exist.dom.persistent.DocumentImpl;
import org.exist.dom.QName;
import org.exist.repo.ExistPkgInfo;
import org.exist.repo.ExistRepository;
import org.exist.security.PermissionDeniedException;
import org.exist.repo.ClasspathHelper;
import org.exist.storage.NativeBroker;
import org.exist.storage.lock.Lock.LockMode;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.BasicFunction;
import org.exist.xquery.Cardinality;
import org.exist.xquery.FunctionSignature;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.BooleanValue;
import org.exist.xquery.value.FunctionParameterSequenceType;
import org.exist.xquery.value.FunctionReturnSequenceType;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.StringValue;
import org.exist.xquery.value.Type;
import org.expath.pkg.repo.*;
import org.expath.pkg.repo.Package;
import org.expath.pkg.repo.tui.BatchUserInteraction;
/**
* Install Function: Install package into repository
*
* @author James Fuller <jim.fuller@exist-db.org>
* @author Wolfgang Meier
* @author ljo
*/
public class InstallFunction extends BasicFunction {
private final static Logger logger = LogManager.getLogger(InstallFunction.class);
public final static FunctionSignature signatureInstall =
new FunctionSignature(
new QName("install", ExpathPackageModule.NAMESPACE_URI, ExpathPackageModule.PREFIX),
"Install package from repository.",
new SequenceType[] { new FunctionParameterSequenceType("pkgName", Type.STRING, Cardinality.EXACTLY_ONE, "package name")},
new FunctionReturnSequenceType(Type.BOOLEAN, Cardinality.EXACTLY_ONE, "true if successful, false otherwise"));
public final static FunctionSignature signatureInstallFromDB =
new FunctionSignature(
new QName("install-from-db", ExpathPackageModule.NAMESPACE_URI, ExpathPackageModule.PREFIX),
"Install package stored in database.",
new SequenceType[] { new FunctionParameterSequenceType("path", Type.STRING, Cardinality.EXACTLY_ONE, "database path to the package archive (.xar file)")},
new FunctionReturnSequenceType(Type.BOOLEAN, Cardinality.EXACTLY_ONE, "true if successful, false otherwise"));
public InstallFunction(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}
public Sequence eval(Sequence[] args, Sequence contextSequence)
throws XPathException {
Sequence removed = BooleanValue.FALSE;
boolean force = true;
UserInteractionStrategy interact = new BatchUserInteraction();
String pkgOrPath = args[0].getStringValue();
Optional<ExistRepository> repo = getContext().getRepository();
try {
if (repo.isPresent()) {
Repository parent_repo = repo.get().getParentRepo();
Package pkg;
if (isCalledAs("install")) {
// download .xar from a URI
URI uri = _getURI(pkgOrPath);
pkg = parent_repo.installPackage(uri, force, interact);
repo.get().reportAction(ExistRepository.Action.INSTALL, pkg.getName());
} else {
// .xar is stored as a binary resource
BinaryDocument doc = null;
try {
doc = _getDocument(pkgOrPath);
Path file = ((NativeBroker)context.getBroker()).getCollectionBinaryFileFsPath(doc.getURI());
LOG.debug("Installing file: " + file.toAbsolutePath().toString());
pkg = parent_repo.installPackage(file, force, interact);
repo.get().reportAction(ExistRepository.Action.INSTALL, pkg.getName());
} finally {
if (doc != null)
doc.getUpdateLock().release(LockMode.READ_LOCK);
}
}
ExistPkgInfo info = (ExistPkgInfo) pkg.getInfo("exist");
if (info != null && !info.getJars().isEmpty())
ClasspathHelper.updateClasspath(context.getBroker().getBrokerPool(), pkg);
// TODO: expath libs do not provide a way to see if there were any XQuery modules installed at all
context.getBroker().getBrokerPool().getXQueryPool().clear();
removed = BooleanValue.TRUE;
} else {
throw new XPathException("expath repository not available");
}
} catch (PackageException ex ) {
logger.error(ex.getMessage(), ex);
return removed;
// /TODO: _repo.removePackage seems to throw PackageException
//throw new XPathException("Problem installing package " + pkg + " in expath repository, check that eXist-db has access permissions to expath repository file directory ", ex);
} catch (XPathException xpe) {
logger.error(xpe.getMessage());
return removed;
}
return removed;
}
private URI _getURI(String s) throws XPathException
{
URI uri;
try {
uri = new URI(s);
}
catch ( URISyntaxException ex ) {
throw new XPathException(this, EXPathErrorCode.EXPDY001, s + " is not a valid URI: " + ex.getMessage(), new StringValue(s), ex);
}
if ( uri.isAbsolute() ) {
return uri;
} else {
throw new XPathException(this, EXPathErrorCode.EXPDY001, s + " must be an absolute URI", new StringValue(s));
}
}
private BinaryDocument _getDocument(String path) throws XPathException {
try {
XmldbURI uri = XmldbURI.createInternal(path);
DocumentImpl doc = context.getBroker().getXMLResource(uri, LockMode.READ_LOCK);
if (doc == null) {
throw new XPathException(this, EXPathErrorCode.EXPDY001,
path + " is not .xar resource",
new StringValue(path)
);
} else if (doc.getResourceType() != DocumentImpl.BINARY_FILE) {
doc.getUpdateLock().release(LockMode.READ_LOCK);
throw new XPathException(this, EXPathErrorCode.EXPDY001,
path + " is not a valid .xar, it's not a binary resource",
new StringValue(path)
);
}
return (BinaryDocument) doc;
} catch (PermissionDeniedException e) {
throw new XPathException(this, EXPathErrorCode.EXPDY003, e.getMessage(), new StringValue(path), e);
}
}
}