/****************************************************************************/
/* File: ExistRepository.java */
/* Author: F. Georges - H2O Consulting */
/* Date: 2010-09-22 */
/* Tags: */
/* Copyright (c) 2010 Florent Georges (see end of file.) */
/* ------------------------------------------------------------------------ */
package org.exist.repo;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.storage.BrokerPool;
import org.exist.storage.BrokerPoolService;
import org.exist.storage.BrokerPoolServiceException;
import org.exist.storage.NativeBroker;
import org.exist.util.Configuration;
import org.exist.util.FileUtils;
import org.exist.xquery.Module;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.expath.pkg.repo.FileSystemStorage;
import org.expath.pkg.repo.FileSystemStorage.FileSystemResolver;
import org.expath.pkg.repo.Package;
import org.expath.pkg.repo.Packages;
import org.expath.pkg.repo.PackageException;
import org.expath.pkg.repo.Repository;
import org.expath.pkg.repo.URISpace;
/**
* A repository as viewed by eXist.
*
* @author Florent Georges
* @author Wolfgang Meier
* @author Adam Retter
* @since 2010-09-22
*/
public class ExistRepository extends Observable implements BrokerPoolService {
private final static Logger LOG = LogManager.getLogger(ExistRepository.class);
public final static String EXPATH_REPO_DIR = "expathrepo";
public final static String EXPATH_REPO_DEFAULT = "webapp/WEB-INF/" + EXPATH_REPO_DIR;
/** The wrapped EXPath repository. */
private Path expathDir;
private Repository myParent;
@Override
public void configure(final Configuration configuration) throws BrokerPoolServiceException {
final Path dataDir = Optional.ofNullable((Path) configuration.getProperty(BrokerPool.PROPERTY_DATA_DIR))
.orElse(Paths.get(NativeBroker.DEFAULT_DATA_DIR));
this.expathDir = dataDir.resolve(EXPATH_REPO_DIR);
}
@Override
public void prepare(final BrokerPool brokerPool) throws BrokerPoolServiceException {
if(!Files.exists(expathDir)) {
moveOldRepo(brokerPool.getConfiguration().getExistHome(), expathDir);
}
try {
Files.createDirectories(expathDir);
} catch(final IOException e) {
throw new BrokerPoolServiceException("Unable to access EXPath repository", e);
}
LOG.info("Using directory " + expathDir.toAbsolutePath().toString() + " for expath package repository");
try {
final FileSystemStorage storage = new FileSystemStorage(expathDir.toFile());
storage.setErrorIfNoContentDir(false);
this.myParent = new Repository(storage);
myParent.registerExtension(new ExistPkgExtension());
} catch(final PackageException e) {
throw new BrokerPoolServiceException("Unable to prepare EXPath Package Repository: " + expathDir.toAbsolutePath().toString(), e);
}
}
public Repository getParentRepo() {
return myParent;
}
public Module resolveJavaModule(final String namespace, final XQueryContext ctxt) throws XPathException {
final URI uri;
try {
uri = new URI(namespace);
}
catch (final URISyntaxException ex) {
throw new XPathException("Invalid URI: " + namespace, ex);
}
for (final Packages pp : myParent.listPackages()) {
final Package pkg = pp.latest();
final ExistPkgInfo info = (ExistPkgInfo) pkg.getInfo("exist");
if (info != null) {
final String clazz = info.getJava(uri);
if (clazz != null) {
return getModule(clazz, namespace, ctxt);
}
}
}
return null;
}
/**
* Load a module instance from its class name. Check the namespace is consistent.
*/
private Module getModule(final String name, final String namespace, final XQueryContext ctxt)
throws XPathException {
try {
final Class<Module> clazz = (Class<Module>)Class.forName(name);
final Module module = instantiateModule(clazz);
final String ns = module.getNamespaceURI();
if (!ns.equals(namespace)) {
throw new XPathException("The namespace in the Java module " +
"does not match the namespace in the package descriptor: " +
namespace + " - " + ns);
}
return ctxt.loadBuiltInModule(namespace, name);
} catch (final ClassNotFoundException ex) {
throw new XPathException("Cannot find module class from EXPath repository: " + name, ex);
} catch (final InstantiationException | InvocationTargetException | IllegalAccessException ex) {
throw new XPathException("Problem instantiating module class from EXPath repository: " + name, ex);
} catch (final ClassCastException ex) {
throw new XPathException("The class configured in EXPath repository is not a Module: " + name, ex);
} catch (final IllegalArgumentException ex) {
throw new XPathException("Illegal argument passed to the module ctor", ex);
}
}
/**
* Try to instantiate the class using the constructor with a Map parameter,
* or the default constructor.
*/
private Module instantiateModule(final Class<Module> clazz) throws XPathException,
InstantiationException, IllegalAccessException, InvocationTargetException {
try {
final Constructor<Module> ctor = clazz.getConstructor(Map.class);
return ctor.newInstance(Collections.emptyMap());
} catch (final NoSuchMethodException ex) {
try {
final Constructor<Module> ctor = clazz.getConstructor();
return ctor.newInstance();
}
catch (final NoSuchMethodException exx) {
throw new XPathException("Cannot find suitable constructor " +
"for module from expath repository", exx);
}
}
}
public Path resolveXQueryModule(final String namespace) throws XPathException {
final URI uri;
try {
uri = new URI(namespace);
} catch (final URISyntaxException ex) {
throw new XPathException("Invalid URI: " + namespace, ex);
}
for (final Packages pp : myParent.listPackages()) {
final Package pkg = pp.latest();
// FIXME: Rely on having a file system storage, that's probably a bad design!
final FileSystemResolver resolver = (FileSystemResolver) pkg.getResolver();
final ExistPkgInfo info = (ExistPkgInfo) pkg.getInfo("exist");
if (info != null) {
final String f = info.getXQuery(uri);
if (f != null) {
return resolver.resolveComponentAsFile(f).toPath();
}
}
String sysid = null; // declared here to be used in catch
try {
final Source src = pkg.resolve(namespace, URISpace.XQUERY);
if (src != null) {
sysid = src.getSystemId();
return Paths.get(new URI(sysid));
}
} catch (final URISyntaxException ex) {
throw new XPathException("Error parsing the URI of the query library: " + sysid, ex);
} catch (final PackageException ex) {
throw new XPathException("Error resolving the query library: " + namespace, ex);
}
}
return null;
}
public List<URI> getJavaModules() {
final List<URI> modules = new ArrayList<>();
for (final Packages pp : myParent.listPackages()) {
final Package pkg = pp.latest();
final ExistPkgInfo info = (ExistPkgInfo) pkg.getInfo("exist");
if (info != null) {
modules.addAll(info.getJavaModules());
}
}
return modules;
}
public static Path getRepositoryDir(final Configuration config) throws IOException {
final Path dataDir = Optional.ofNullable((Path) config.getProperty(BrokerPool.PROPERTY_DATA_DIR))
.orElse(Paths.get(NativeBroker.DEFAULT_DATA_DIR));
final Path expathDir = dataDir.resolve(EXPATH_REPO_DIR);
if(!Files.exists(expathDir)) {
moveOldRepo(config.getExistHome(), expathDir);
}
Files.createDirectories(expathDir);
return expathDir;
}
private static void moveOldRepo(final Optional<Path> home, final Path newRepo) {
final Path repo_dir = home.map(h -> {
if(FileUtils.fileName(h).equals("WEB-INF")) {
return h.resolve(EXPATH_REPO_DIR);
} else {
return h.resolve( EXPATH_REPO_DEFAULT);
}
}).orElse(Paths.get(System.getProperty("java.io.tmpdir")).resolve(EXPATH_REPO_DIR));
if (Files.isReadable(repo_dir)) {
LOG.info("Found old expathrepo directory. Moving to new default location: " + newRepo.toAbsolutePath().toString());
try {
Files.move(repo_dir, newRepo, StandardCopyOption.ATOMIC_MOVE);
} catch (final IOException e) {
LOG.error("Failed to move old expathrepo directory to new default location. Keeping it.", e);
}
}
}
public void reportAction(final Action action, final String packageURI) {
notifyObservers(new Notification(action, packageURI));
setChanged();
}
public enum Action {
INSTALL, UNINSTALL
}
public final static class Notification {
private final Action action;
private final String packageURI;
public Notification(final Action action, final String packageURI) {
this.action = action;
this.packageURI = packageURI;
}
public Action getAction() {
return action;
}
public String getPackageURI() {
return packageURI;
}
}
}
/* ------------------------------------------------------------------------ */
/* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS COMMENT. */
/* */
/* The contents of this file are subject to the Mozilla Public License */
/* Version 1.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.mozilla.org/MPL/. */
/* */
/* Software distributed under the License is distributed on an "AS IS" */
/* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See */
/* the License for the specific language governing rights and limitations */
/* under the License. */
/* */
/* The Original Code is: all this file. */
/* */
/* The Initial Developer of the Original Code is Florent Georges. */
/* */
/* Contributor(s): Wolfgang Meier, Adam Retter */
/* ------------------------------------------------------------------------ */