blob: 60efdd91712e1054534f974afac3b70a5b2c2fb0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.vault.packaging.impl;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
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.jackrabbit.vault.fs.api.ProgressTrackerListener;
import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
import org.apache.jackrabbit.vault.fs.config.DefaultMetaInf;
import org.apache.jackrabbit.vault.fs.impl.ArchiveWrapper;
import org.apache.jackrabbit.vault.fs.impl.SubPackageFilterArchive;
import org.apache.jackrabbit.vault.fs.io.Archive;
import org.apache.jackrabbit.vault.fs.io.ImportOptions;
import org.apache.jackrabbit.vault.fs.io.ZipStreamArchive;
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.NoSuchPackageException;
import org.apache.jackrabbit.vault.packaging.PackageException;
import org.apache.jackrabbit.vault.packaging.PackageExistsException;
import org.apache.jackrabbit.vault.packaging.PackageId;
import org.apache.jackrabbit.vault.packaging.VaultPackage;
import org.apache.jackrabbit.vault.packaging.events.PackageEvent;
import org.apache.jackrabbit.vault.packaging.events.impl.PackageEventDispatcher;
import org.apache.jackrabbit.vault.packaging.registry.PackageRegistry;
import org.apache.jackrabbit.vault.packaging.registry.impl.JcrPackageRegistry;
import org.apache.jackrabbit.vault.packaging.registry.impl.JcrRegisteredPackage;
import org.apache.jackrabbit.vault.util.JcrConstants;
import static org.apache.jackrabbit.vault.packaging.registry.impl.JcrPackageRegistry.DEFAULT_PACKAGE_ROOT_PATH;
/**
* Extends the {@code PackageManager} by JCR specific methods
*/
public class JcrPackageManagerImpl extends PackageManagerImpl implements JcrPackageManager {
/**
* root path for packages
*/
public final static String ARCHIVE_PACKAGE_ROOT_PATH = "/jcr_root/etc/packages";
/**
* internal package persistence
*/
private final JcrPackageRegistry registry;
/**
* Creates a new package manager using the given session. This method allows to specify one more or package
* registry root paths, where the first will be the primary when installing new packages. The others server as
* backward compatibility to read existing packages.
*
* @param session repository session
* @param roots the root paths to store the packages.
*/
public JcrPackageManagerImpl(Session session, String[] roots) {
this(new JcrPackageRegistry(session, roots));
}
private JcrPackageManagerImpl(JcrPackageRegistry registry) {
this.registry = registry;
}
private RepositoryException unwrapRepositoryException(Exception e) {
if (e.getCause() instanceof RepositoryException) {
return (RepositoryException) e.getCause();
}
return new RepositoryException(e);
}
public PackageRegistry getRegistry() {
return registry;
}
public JcrPackageRegistry getInternalRegistry() {
return registry;
}
/**
* {@inheritDoc}
*/
@Override
public JcrPackage open(PackageId id) throws RepositoryException {
try {
//noinspection resource
JcrRegisteredPackage pkg = (JcrRegisteredPackage) registry.open(id);
return pkg == null ? null : pkg.getJcrPackage();
} catch (IOException e) {
throw unwrapRepositoryException(e);
}
}
/**
* {@inheritDoc}
*/
@Override
public JcrPackage open(Node node) throws RepositoryException {
return registry.open(node, false);
}
/**
* {@inheritDoc}
*/
@Override
public JcrPackage open(Node node, boolean allowInvalid) throws RepositoryException {
return registry.open(node, allowInvalid);
}
/**
* {@inheritDoc}
*/
@Override
public PackageId resolve(Dependency dependency, boolean onlyInstalled) throws RepositoryException {
try {
return registry.resolve(dependency, onlyInstalled);
} catch (IOException e) {
throw unwrapRepositoryException(e);
}
}
/**
* {@inheritDoc}
*/
@Override
public PackageId[] usage(PackageId id) throws RepositoryException {
try {
return registry.usage(id);
} catch (IOException e) {
throw unwrapRepositoryException(e);
}
}
/**
* {@inheritDoc}
*/
@Override
public JcrPackage upload(InputStream in, boolean replace) throws RepositoryException, IOException {
return upload(in, replace, false);
}
/**
* {@inheritDoc}
*/
@Override
public JcrPackage upload(InputStream in, boolean replace, boolean strict)
throws RepositoryException, IOException {
try {
return registry.upload(in, replace);
} catch (PackageExistsException e) {
throw new ItemExistsException(e.getMessage(), e);
}
}
/**
* {@inheritDoc}
*/
@Nonnull
@Override
public PackageId[] extract(@Nonnull Archive archive, @Nonnull ImportOptions options, boolean replace)
throws RepositoryException, PackageException, IOException {
SubPackageFilterArchive spfArchive = null;
if (!options.isNonRecursive()) {
spfArchive = new SubPackageFilterArchive(archive);
archive = spfArchive;
} else {
archive = new ArchiveWrapper(archive);
}
ZipVaultPackage pkg = new ZipVaultPackage(archive, true);
PackageId pid = pkg.getId();
JcrPackage jcrPack = registry.upload(pkg, replace);
jcrPack = new JcrPackageImpl(registry, jcrPack.getNode(), pkg);
jcrPack.extract(options);
Set<PackageId> ids = new HashSet<>();
ids.add(pid);
if (spfArchive != null) {
for (Archive.Entry e: spfArchive.getSubPackageEntries()) {
InputStream in = spfArchive.openInputStream(e);
if (in != null) {
try (Archive subArchive = new ZipStreamArchive(in)) {
PackageId[] subIds = extract(subArchive, options, replace);
ids.addAll(Arrays.asList(subIds));
}
}
}
}
pkg.close();
jcrPack.close();
return ids.toArray(new PackageId[ids.size()]);
}
/**
* {@inheritDoc}
*/
@Override
public JcrPackage upload(File file, boolean isTmpFile, boolean replace, String nameHint)
throws RepositoryException, IOException {
return upload(file, isTmpFile, replace, nameHint, false);
}
/**
* {@inheritDoc}
*/
@Override
public JcrPackage upload(File file, boolean isTmpFile, boolean replace, String nameHint, boolean strict)
throws RepositoryException, IOException {
ZipVaultPackage pack = new ZipVaultPackage(file, isTmpFile, strict);
try {
return registry.upload(pack, replace);
} catch (PackageExistsException e) {
throw new ItemExistsException(e.getMessage(), e);
}
}
/**
* {@inheritDoc}
*/
@Override
public JcrPackage create(Node folder, String name)
throws RepositoryException, IOException {
if (folder == null) {
folder = getPackageRoot();
}
return registry.createNew(folder, new PackageId(name), null, true);
}
/**
* {@inheritDoc}
*/
@Override
public JcrPackage create(String group, String name)
throws RepositoryException, IOException {
return create(group, name, null);
}
/**
* {@inheritDoc}
*/
@Override
public JcrPackage create(String group, String name, String version)
throws RepositoryException, IOException {
return registry.create(group, name, version);
}
/**
* {@inheritDoc}
*/
@Override
public void remove(JcrPackage pack) throws RepositoryException {
try {
registry.remove(pack.getDefinition().getId());
} catch (IOException e) {
throw unwrapRepositoryException(e);
} catch (NoSuchPackageException e) {
// old implementation ignored this ignore
}
}
/**
* {@inheritDoc}
*/
@Override
public JcrPackage rename(JcrPackage pack, String group, String name)
throws PackageException, RepositoryException {
return rename(pack, group, name, null);
}
/**
* {@inheritDoc}
*/
@Override
public JcrPackage rename(JcrPackage pack, String group, String name, String version)
throws PackageException, RepositoryException {
return registry.rename(pack, group, name, version);
}
/**
* {@inheritDoc}
*/
@Override
public void assemble(JcrPackage pack, ProgressTrackerListener listener)
throws PackageException, RepositoryException, IOException {
pack.verifyId(true, true);
assemble(pack.getNode(), pack.getDefinition(), listener);
}
/**
* {@inheritDoc}
*/
@Override
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);
DefaultMetaInf inf = (DefaultMetaInf) def.getMetaInf();
CompositeExportProcessor processor = new CompositeExportProcessor();
// tweak filter for primary root, in case it contains sub-packages
if (!registry.getPackRootPaths()[0].equals(DEFAULT_PACKAGE_ROOT_PATH)) {
SubPackageExportProcessor sp = new SubPackageExportProcessor(this, packNode.getSession());
WorkspaceFilter newFilter = sp.prepare(inf.getFilter());
if (newFilter != null) {
inf.setFilter(newFilter);
processor.addProcessor(sp);
}
}
ExportOptions opts = new ExportOptions();
opts.setMetaInf(inf);
opts.setListener(listener);
processor.addProcessor(def.getInjectProcessor());
opts.setPostProcessor(processor);
VaultPackage pack = assemble(packNode.getSession(), opts, (File) null);
PackageId id = pack.getId();
// update this content
Node contentNode = packNode.getNode(JcrConstants.JCR_CONTENT);
try (InputStream in = FileUtils.openInputStream(pack.getFile())){
// 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.getSession().save();
} catch (IOException e) {
throw new PackageException(e);
}
pack.close();
dispatch(PackageEvent.Type.ASSEMBLE, id, null);
}
/**
* {@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}
*/
@Override
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);
ExportOptions opts = new ExportOptions();
opts.setMetaInf(def.getMetaInf());
opts.setListener(listener);
opts.setPostProcessor(def.getInjectProcessor());
assemble(def.getNode().getSession(), opts, out);
}
/**
* {@inheritDoc}
*/
@Override
public void rewrap(JcrPackage pack, ProgressTrackerListener listener)
throws PackageException, RepositoryException, IOException {
VaultPackage src = ((JcrPackageImpl) pack).getPackage(true);
Calendar now = Calendar.getInstance();
pack.verifyId(true, false);
JcrPackageDefinitionImpl def = (JcrPackageDefinitionImpl) pack.getDefinition();
def.sealForRewrap(now);
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.getSession().save();
dst.close();
}
/**
* yet another Convenience method to create intermediate nodes.
* @param path path to create
* @param autoSave if {@code true} all changes are automatically persisted
* @return the node
* @throws RepositoryException if an error occurrs
*/
protected Node mkdir(String path, boolean autoSave) throws RepositoryException {
return registry.mkdir(path, autoSave);
}
/**
* {@inheritDoc}
*/
@Override
public Node getPackageRoot() throws RepositoryException {
return registry.getPrimaryPackageRoot(true);
}
/**
* {@inheritDoc}
*/
@Override
public Node getPackageRoot(boolean noCreate) throws RepositoryException {
return registry.getPrimaryPackageRoot(!noCreate);
}
/**
* {@inheritDoc}
*/
@Override
public List<JcrPackage> listPackages() throws RepositoryException {
return listPackages(null);
}
/**
* {@inheritDoc}
*/
@Override
public List<JcrPackage> listPackages(WorkspaceFilter filter) throws RepositoryException {
List<JcrPackage> packages = new LinkedList<JcrPackage>();
for (Node root: registry.getPackageRoots()) {
listPackages(root, packages, filter, false, false);
}
Collections.sort(packages);
return packages;
}
/**
* {@inheritDoc}
*/
@Override
public List<JcrPackage> listPackages(String group, boolean built) throws RepositoryException {
List<JcrPackage> packages = new LinkedList<JcrPackage>();
for (Node root: registry.getPackageRoots()) {
listPackages(root, packages, group, built);
}
Collections.sort(packages);
return packages;
}
/**
* internally lists all the packages below the given package root and adds them to the given list.
* @param pkgRoot the package root
* @param packages the list of packages
* @param group optional group to search below
* @param built if {@code true} only packages with size > 0 are returned
* @throws RepositoryException if an error occurrs
*/
private void listPackages(Node pkgRoot, List<JcrPackage> packages, String group, boolean built) throws RepositoryException {
if (group == null || group.length() == 0) {
listPackages(pkgRoot, packages, null, built, false);
} else {
if (group.equals(pkgRoot.getPath())) {
group = "";
} else if (group.startsWith(pkgRoot.getPath() + "/")) {
group = group.substring(pkgRoot.getPath().length() + 1);
}
if (group.startsWith("/")) {
// don't scan outside the roots
return;
}
Node root = pkgRoot;
if (group.length() > 0) {
if (root.hasNode(group)) {
root = root.getNode(group);
} else {
return;
}
}
listPackages(root, packages, null, built, true);
}
}
/**
* internally adds the packages below {@code root} 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} only packages with size > 0 are returned
* @param shallow if {@code true} 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 (".snapshot".equals(child.getName())) {
continue;
}
JcrPackageImpl pack = new JcrPackageImpl(registry, child);
if (pack.isValid()) {
// skip packages with illegal names
JcrPackageDefinition jDef = pack.getDefinition();
if (jDef != null && !jDef.getId().isValid()) {
continue;
}
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, false);
}
}
}
}
@Override
public void setDispatcher(@Nullable PackageEventDispatcher dispatcher) {
super.setDispatcher(dispatcher);
registry.setDispatcher(dispatcher);
}
}