blob: 996b64d9116c0737ddf5a8ad7b92bdb607a65643 [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 org.apache.jackrabbit.vault.fs.api.PathMapping;
import org.apache.jackrabbit.vault.fs.api.VaultInputSource;
import org.apache.jackrabbit.vault.fs.config.MetaInf;
import org.apache.jackrabbit.util.Text;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Implements an archive that remaps the entries of an underlying archive using a {@link PathMapping}.
*/
public class MappedArchive extends AbstractArchive {
/**
* default logger
*/
private static final Logger log = LoggerFactory.getLogger(MappedArchive.class);
private final Archive base;
private final PathMapping mapping;
private final VirtualEntry root = new VirtualEntry();
private VirtualEntry jcrRoot;
public MappedArchive(Archive base, PathMapping mapping) {
this.base = base;
this.mapping = mapping;
}
@Override
public void open(boolean strict) throws IOException {
base.open(strict);
applyMapping(base.getRoot(), root);
}
/**
* Decorates the non jcr files without applying the mapping. this can be done with parallel traversal.
* @param src source entry parent
* @param dst destination entry parent
*/
private void applyMapping(@NotNull Entry src, @NotNull VirtualEntry dst) {
for (Entry child: src.getChildren()) {
VirtualEntry dstChild = dst.add(child.getName(), child);
if ("/jcr_root".equals(dstChild.getNodePath())) {
jcrRoot = dstChild;
applyMapping(child, "");
} else {
applyMapping(child, dstChild);
}
}
}
/**
* Decorates the jcr entries while applying the path mapping. this cannot be done with parallel traversal, because
* the remapping could create holes in the entry tree.
*
* @param src the source entry
* @param jcrPath the jcr path of the source entry
*/
private void applyMapping(@NotNull Entry src, @NotNull String jcrPath) {
for (Entry child: src.getChildren()) {
// TODO: convert to node name
String path = jcrPath + "/" + child.getName();
String mappedPath = mapping.map(path);
// add entry to tree (convert back to file name format)
String[] segments = Text.explode(mappedPath, '/');
VirtualEntry entry = jcrRoot;
for (String seg: segments) {
entry = entry.add(seg, null);
}
if (entry.baseEntry != null) {
log.warn("Path mapping maps multiple paths to the same destination: {} -> {}. ignoring this source.", path, mappedPath);
} else {
entry.baseEntry = child;
}
applyMapping(child, path);
}
}
@Override
@Nullable
public InputStream openInputStream(@Nullable Entry entry) throws IOException {
if (entry == null) {
return null;
}
return base.openInputStream(((VirtualEntry) entry).baseEntry);
}
@Override
@Nullable
public VaultInputSource getInputSource(@Nullable Entry entry) throws IOException {
if (entry == null) {
return null;
}
return base.getInputSource(((VirtualEntry) entry).baseEntry);
}
@Override
@NotNull
public Entry getRoot() throws IOException {
return root;
}
@Override
public Entry getJcrRoot() throws IOException {
return jcrRoot;
}
@Override
@NotNull
public MetaInf getMetaInf() {
return base.getMetaInf();
}
@Override
public void close() {
base.close();
}
/**
* Implements a entry for this archive
*/
private static class VirtualEntry implements Entry {
@Nullable
private final VirtualEntry parent;
@NotNull
private final String name;
@Nullable
private Archive.Entry baseEntry;
@Nullable
private Map<String, VirtualEntry> children;
private VirtualEntry() {
this.parent = null;
this.name = "";
this.baseEntry = null;
}
private VirtualEntry(@NotNull VirtualEntry parent, @NotNull String name, @Nullable Archive.Entry baseEntry) {
this.parent = parent;
this.name = name;
this.baseEntry = baseEntry;
}
/**
* {@inheritDoc}
*/
@Override
@NotNull
public String getName() {
return name;
}
/**
*
* @return the JCR node path (only the root node) represented by this entry
*/
@NotNull
public String getNodePath() {
return getNodePath(new StringBuilder()).toString();
}
@NotNull
private StringBuilder getNodePath(@NotNull StringBuilder sb) {
return parent == null ? sb : parent.getNodePath(sb).append('/').append(name);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isDirectory() {
return baseEntry == null || baseEntry.isDirectory();
}
/**
* {@inheritDoc}
*/
@Override
@NotNull
public Collection<? extends Entry> getChildren() {
return children == null ? Collections.<Entry>emptyList() : children.values();
}
/**
* {@inheritDoc}
*/
@Override
@Nullable
public Entry getChild(String name) {
return children == null ? null : children.get(name);
}
/**
* Adds a new child entry.
* @param name name
* @param baseEntry the base archive's entry or
* @return the new entry
*/
@NotNull
public VirtualEntry add(@NotNull String name, @Nullable Archive.Entry baseEntry) {
if (children != null) {
VirtualEntry ret = children.get(name);
if (ret != null) {
return ret;
}
}
VirtualEntry ve = new VirtualEntry(this, name, baseEntry);
if (children == null) {
children = new LinkedHashMap<>();
}
children.put(name, ve);
return ve;
}
}
}