blob: eb471094cca4c67f311180426bca2a8717d441d5 [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.fs.io;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import org.apache.jackrabbit.vault.fs.api.PathFilterSet;
import org.apache.jackrabbit.vault.fs.api.VaultInputSource;
import org.apache.jackrabbit.vault.fs.config.ConfigurationException;
import org.apache.jackrabbit.vault.fs.config.DefaultMetaInf;
import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
import org.apache.jackrabbit.vault.fs.config.MetaInf;
import org.apache.jackrabbit.vault.fs.config.VaultSettings;
import org.apache.jackrabbit.vault.util.Constants;
import org.apache.jackrabbit.vault.util.JcrConstants;
import org.apache.jackrabbit.vault.util.Text;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implements an archive that is based on nt:file/nt:folder nodes in a JCR
* repository.
*/
public class JcrArchive extends AbstractArchive {
/**
* default logger
*/
private static final Logger log = LoggerFactory.getLogger(JcrArchive.class);
private Node archiveRoot;
private final String rootPath;
private DefaultMetaInf inf;
private Entry jcrRoot;
// the 'chrooted' root. currently not used.
private String chRoot;
/**
* Creates a new JCR archive rooted at the given node.
* @param archiveRoot root node for this archive
* @param rootPath root path
*/
public JcrArchive(@Nonnull Node archiveRoot, @Nonnull String rootPath) {
this.archiveRoot = archiveRoot;
this.rootPath = rootPath;
}
/**
* {@inheritDoc}
*/
@Override
public void open(boolean strict) throws IOException {
if (jcrRoot != null) {
return;
}
try {
if (archiveRoot.hasNode(Constants.ROOT_DIR)) {
jcrRoot = new JcrEntry(archiveRoot.getNode(Constants.ROOT_DIR), Constants.ROOT_DIR, true);
} else {
jcrRoot = new JcrEntry(archiveRoot, archiveRoot.getName(), true);
}
if (archiveRoot.hasNode(Constants.META_DIR)) {
inf = loadMetaInf(new JcrEntry(archiveRoot.getNode(Constants.META_DIR), Constants.META_DIR, true));
} else {
inf = new DefaultMetaInf();
inf.setSettings(VaultSettings.createDefault());
DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter();
PathFilterSet filterSet = new PathFilterSet(rootPath);
filter.add(filterSet);
inf.setFilter(filter);
// if archive is ch-rooted, create intermediate entries
if (chRoot != null && chRoot.length() > 0) {
String[] roots = Text.explode(rootPath, '/');
if (roots.length > 0) {
VirtualEntry newRoot = new VirtualEntry(jcrRoot.getName());
VirtualEntry entry = newRoot;
for (String name: roots) {
VirtualEntry newEntry = new VirtualEntry(name);
entry.children.put(name, newEntry);
entry = newEntry;
}
for (Entry e: jcrRoot.getChildren()) {
entry.children.put(e.getName(), e);
}
jcrRoot = newRoot;
}
}
}
} catch (RepositoryException | ConfigurationException e) {
throw new IOException("Error while opening JCR archive.", e);
}
}
private DefaultMetaInf loadMetaInf(Entry dir)
throws IOException, ConfigurationException {
DefaultMetaInf inf = new DefaultMetaInf();
// filter
for (Entry entry: dir.getChildren()) {
VaultInputSource src = getInputSource(entry);
try (InputStream input = src.getByteStream()) {
inf.load(input, src.getSystemId());
}
}
if (inf.getFilter() == null) {
log.debug("Archive {} does not contain filter definition.", this);
}
if (inf.getConfig() == null) {
log.debug("Archive {} does not contain vault config.", this);
}
if (inf.getSettings() == null) {
log.debug("Archive {} does not contain vault settings. using default.", this);
VaultSettings settings = new VaultSettings();
settings.getIgnoredNames().add(".svn");
inf.setSettings(settings);
}
if (inf.getProperties() == null) {
log.debug("Archive {} does not contain properties.", this);
}
if (inf.getNodeTypes().isEmpty()) {
log.debug("Archive {} does not contain nodetypes.", this);
}
return inf;
}
/**
* {@inheritDoc}
*/
@Override
public void close() {
archiveRoot = null;
jcrRoot = null;
}
/**
* {@inheritDoc}
*/
@Override
public Entry getJcrRoot() {
return jcrRoot;
}
/**
* {@inheritDoc}
*/
@Override
public Entry getRoot() throws IOException {
return new JcrEntry(archiveRoot, "", true);
}
/**
* {@inheritDoc}
*/
@Override
public MetaInf getMetaInf() {
return inf;
}
/**
* {@inheritDoc}
*/
@Override
public InputStream openInputStream(Entry entry) throws IOException {
if (entry == null || entry.isDirectory()) {
return null;
}
try {
Node content = ((JcrEntry) entry).node.getNode(JcrConstants.JCR_CONTENT);
return content.getProperty(JcrConstants.JCR_DATA).getBinary().getStream();
} catch (RepositoryException e) {
throw new IOException("Unable to open input source.", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public VaultInputSource getInputSource(final Entry entry) throws IOException {
if (entry == null || entry.isDirectory()) {
return null;
}
try {
final Node content = ((JcrEntry) entry).node.getNode(JcrConstants.JCR_CONTENT);
final String systemId = ((JcrEntry) entry).node.getPath();
return new VaultInputSource() {
{
setSystemId(systemId);
}
public InputStream getByteStream() {
try {
// todo: handle releasing of binary ?
return content.getProperty(JcrConstants.JCR_DATA).getBinary().getStream();
} catch (RepositoryException e) {
log.error("Error while opening input stream of " + content, e);
return null;
}
}
/**
* {@inheritDoc}
*/
public long getContentLength() {
try {
return content.getProperty(JcrConstants.JCR_DATA).getLength();
} catch (RepositoryException e) {
log.error("Error while retrieving length of " + content, e);
return -1;
}
}
/**
* {@inheritDoc}
*/
public long getLastModified() {
try {
return content.getProperty(JcrConstants.JCR_LASTMODIFIED).getDate().getTimeInMillis();
} catch (RepositoryException e) {
log.error("Error while retrieving last modified of " + content, e);
return 0;
}
}
};
} catch (RepositoryException e) {
throw new IOException("Unable to open input source.", e);
}
}
@Override
public String toString() {
try {
return archiveRoot.getPath();
} catch (RepositoryException e) {
return archiveRoot.toString();
}
}
/**
* Implements a virtual entry for the intermediate directories
*/
private static class VirtualEntry implements Entry {
private final String name;
private Map<String, Entry> children = new LinkedHashMap<String, Entry>();
private VirtualEntry(String name) {
this.name = name;
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return name;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isDirectory() {
return true;
}
/**
* {@inheritDoc}
*/
@Override
public Collection<? extends Entry> getChildren() {
return children.values();
}
/**
* {@inheritDoc}
*/
@Override
public Entry getChild(String name) {
return children.get(name);
}
}
/**
* Implements an archive entry that is based on a JCR node
*/
private static class JcrEntry implements Entry {
private final Node node;
private final boolean isDir;
private final String name;
private JcrEntry(Node node, String name, boolean isDir) {
this.node = node;
this.isDir = isDir;
this.name = name;
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return name;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isDirectory() {
return isDir;
}
/**
* {@inheritDoc}
*/
@Override
public Collection<Entry> getChildren() {
if (isDir) {
try {
NodeIterator iter = node.getNodes();
long size = iter.getSize();
if (size < 0) {
size = 0;
}
List<Entry> ret = new ArrayList<Entry>((int) size);
while (iter.hasNext()) {
Node child = iter.nextNode();
String name = child.getName();
if (".svn".equals(name)) {
// skip already
continue;
}
boolean isDir;
if (child.isNodeType("nt:folder")) {
isDir = true;
} else if (child.isNodeType("nt:file")) {
isDir = false;
} else {
log.debug("Skipping node {} with unknown type {}.", child.getPath(), child.getPrimaryNodeType().getName());
continue;
}
ret.add(new JcrEntry(child, name, isDir));
}
return ret;
} catch (RepositoryException e) {
log.error("Error while listing child nodes of {}", node, e);
throw new IllegalStateException("Error while listing child nodes of " + node, e);
}
}
return Collections.emptyList();
}
/**
* {@inheritDoc}
*/
@Override
public Entry getChild(String name) {
try {
if (isDir && node.hasNode(name) && !".svn".equals(name)) {
Node child = node.getNode(name);
boolean isDir;
if (child.isNodeType("nt:folder")) {
isDir = true;
} else if (child.isNodeType("nt:file")) {
isDir = false;
} else {
log.debug("Skipping node {} with unknown type {}.", child.getPath(), child.getPrimaryNodeType().getName());
return null;
}
return new JcrEntry(child, name, isDir);
}
return null;
} catch (RepositoryException e) {
log.error("Error while retrieving child node of {}", node, e);
throw new IllegalStateException("Error while retrieving child node of " + node, e);
}
}
}
}