org.apache.jackrabbit.vault.fs.io.ZipNioArchive Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/*
* 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.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.ProviderNotFoundException;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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.MetaInf;
import org.apache.jackrabbit.vault.fs.config.VaultSettings;
import org.apache.jackrabbit.vault.util.Constants;
import org.h2.util.CloseWatcher;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Archive leveraging Java NIO File and its Zip File System Provider.
*
* It doesn't support accessing nested zip files (for subpackages, compare with JDK-8247441)
* @since 3.5.2 (package version 2.11.0)
*/
public class ZipNioArchive extends AbstractArchive {
/**
* default logger
*/
private static final Logger log = LoggerFactory.getLogger(ZipNioArchive.class);
private final Path path;
private final boolean deleteAtClose;
/**
* The (loaded) meta info
*/
private DefaultMetaInf inf;
private FileSystem zipFileSystem;
/** The watcher for unclosed archives */
private CloseWatcher watcher;
/**
* Shortcut for {@link ZipNioArchive#ZipNioArchive(Path, boolean)} with {@code false} as second parameter.
*
* @param path the path of the zip file
*/
public ZipNioArchive(Path path) {
this(path, false);
}
/**
*
* @param path the path of the zip file
* @param deleteAtClose if {@code true} removes the file with the given path during {@link #close()}
*/
public ZipNioArchive(Path path, boolean deleteAtClose) {
super();
this.path = path;
zipFileSystem = null;
this.deleteAtClose = deleteAtClose;
}
@Override
public void open(boolean strict) throws IOException {
if (zipFileSystem != null) {
log.debug("Archive already open");
return;
}
try {
zipFileSystem = FileSystems.newFileSystem(path, (ClassLoader)null);
} catch (ProviderNotFoundException e) {
throw new IOException("Can not open zip file '" + path + "'", e);
}
dumpUnclosedArchives();
watcher = CloseWatcher.register(this, zipFileSystem, SHOULD_CREATE_STACK_TRACE);
}
@Override
@Nullable
public InputStream openInputStream(@Nullable Entry entry) throws IOException {
if (entry == null) {
return null;
}
if (zipFileSystem == null) {
throw new ArchiveNotOpenException(path.toString());
}
return Files.newInputStream(((EntryImpl)entry).path);
}
@Override
@Nullable
public VaultInputSource getInputSource(@Nullable Entry entry) throws IOException {
if (entry == null) {
return null;
}
if (zipFileSystem == null) {
throw new ArchiveNotOpenException(path.toString());
}
final EntryImpl entryImpl = (EntryImpl)entry;
return new VaultInputSourceImpl(entryImpl);
}
@Override
public @NotNull Entry getRoot() throws IOException {
// use first root directory
return new EntryImpl(zipFileSystem.getRootDirectories().iterator().next());
}
@Override
public @NotNull MetaInf getMetaInf() {
if (inf == null) {
try {
inf = parseMetaInf();
} catch (IOException e) {
throw new IllegalStateException("Could not retrieve meta data from " + path.toString(), e);
}
}
return inf;
}
private @NotNull DefaultMetaInf parseMetaInf() throws IOException {
if (zipFileSystem == null) {
throw new ArchiveNotOpenException(path.toString());
}
final DefaultMetaInf defaultMetaInf = new DefaultMetaInf();
// check for meta inf
try (Stream metaDirFiles = Files.list(zipFileSystem.getPath(Constants.META_INF, Constants.VAULT_DIR))) {
Iterator filesIterator = metaDirFiles.iterator();
while (filesIterator.hasNext()) {
Path file = filesIterator.next();
if (Files.isDirectory(file)) {
continue;
}
try (InputStream input = Files.newInputStream(file)) {
defaultMetaInf.load(input, getSystemId(path, file));
} catch (ConfigurationException e1) {
throw new IOException(e1);
}
}
}
if (defaultMetaInf.getFilter() == null) {
log.debug("Zip {} does not contain filter definition.", path);
}
if (defaultMetaInf.getConfig() == null) {
log.debug("Zip {} does not contain vault config.", path);
}
if (defaultMetaInf.getSettings() == null) {
log.debug("Zip {} does not contain vault settings. using default.", path);
VaultSettings settings = new VaultSettings();
settings.getIgnoredNames().add(".svn");
defaultMetaInf.setSettings(settings);
}
if (defaultMetaInf.getProperties() == null) {
log.debug("Zip {} does not contain properties.", path);
}
if (defaultMetaInf.getNodeTypes().isEmpty()) {
log.debug("Zip {} does not contain nodetypes.", path);
}
return defaultMetaInf;
}
private static String getSystemId(Path zipPath, Path pathInZip) {
String systemId = zipPath.toString();
systemId += "!/" + pathInZip;
return systemId;
}
@Override
public void close() {
if (zipFileSystem != null) {
CloseWatcher.unregister(watcher);
try {
zipFileSystem.close();
} catch (IOException e) {
log.warn("Error during close.", e);
}
}
if (deleteAtClose) {
try {
Files.delete(path);
} catch (IOException e) {
log.warn("Could not delete " + path, e);
}
}
}
private static final class VaultInputSourceImpl extends VaultInputSource {
private final EntryImpl entryImpl;
private VaultInputSourceImpl(EntryImpl entryImpl) {
this.entryImpl = entryImpl;
}
@Override
public InputStream getByteStream() {
try {
return Files.newInputStream(entryImpl.path);
} catch (IOException e) {
throw new IllegalStateException("Could not retrieve byte stream", e);
}
}
@Override
public long getContentLength() {
try {
return Files.size(entryImpl.path);
} catch (IOException e) {
throw new IllegalStateException("Could not retrieve file size", e);
}
}
@Override
public long getLastModified() {
try {
return Files.getLastModifiedTime(entryImpl.path).toMillis();
} catch (IOException e) {
throw new IllegalStateException("Could not retrieve last modification time", e);
}
}
}
/**
* Implements the entry for this archive
*/
private static final class EntryImpl implements Entry {
protected final Path path;
private Map children;
private EntryImpl(@NotNull Path path) {
this.path = path;
}
@Override
@NotNull
public String getName() {
int numNames = path.getNameCount();
if (numNames == 0) {
return "";
}
String name = path.getName(numNames-1).toString();
// strip trailing slashes (returned by Zip File System provider for directories)
if (name.endsWith("/")) {
name = name.substring(0, name.length()-1);
}
return name;
}
@Override
public boolean isDirectory() {
return Files.isDirectory(path);
}
@Override
@NotNull
public Collection extends Entry> getChildren() {
if (children == null) {
children = populateChildren();
}
return children.values();
}
private @NotNull Map populateChildren() {
try (Stream childStream = Files.list(path)) {
return childStream.map(EntryImpl::new).collect(Collectors.toMap(EntryImpl::getName, Function.identity()));
} catch (IOException e) {
throw new IllegalStateException("Could not retrieve children", e);
}
}
@Override
@Nullable
public Entry getChild(@NotNull String name) {
if (children == null) {
children = populateChildren();
}
return children.get(name);
}
}
public final class ArchiveNotOpenException extends IllegalStateException {
/**
*
*/
private static final long serialVersionUID = 5634035426424134529L;
public ArchiveNotOpenException(String archivePath) {
super("Archive '" + archivePath + "' not open");
}
}
}