package org.openlca.ilcd.io;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.openlca.ilcd.commons.IDataSet;
import org.openlca.ilcd.sources.Source;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ZipStore implements DataStore {
private Logger log = LoggerFactory.getLogger(getClass());
private FileSystem zip;
private HashMap<String, List<Path>> entries;
private XmlBinder binder = new XmlBinder();
public ZipStore(File zipFile) throws IOException {
log.trace("Create zip store {}", zipFile);
String uriStr = zipFile.toURI().toASCIIString();
URI uri = URI.create("jar:" + uriStr);
Map<String, String> options = new HashMap<>();
if (!zipFile.exists())
options.put("create", "true");
zip = FileSystems.newFileSystem(uri, options);
initEntries();
}
private void initEntries() throws IOException {
entries = new HashMap<>();
for (Path root : zip.getRootDirectories()) {
Files.walkFileTree(root, new FileVisitor(f -> {
Path p = f.getParent();
if (p == null)
return;
Path dirPath = p.getFileName();
if (dirPath == null)
return;
String dir = dirPath.toString().toLowerCase();
List<Path> list = getEntries(dir);
list.add(f);
}));
}
}
/** Get the entries of the given directory. */
public List<Path> getEntries(String dir) {
List<Path> list = entries.get(dir);
if (list == null) {
list = new ArrayList<>();
entries.put(dir, list);
}
return list;
}
public List<Path> getEntries(Class<? extends IDataSet> type) {
return getEntries(Dir.get(type));
}
@Override
public <T> T get(Class<T> type, String id) throws DataStoreException {
log.trace("Get {} for id {} from zip", type, id);
Path entry = findEntry(Dir.get(type), id);
if (entry == null)
return null;
return unmarshal(type, entry);
}
@Override
public void put(IDataSet ds) throws DataStoreException {
log.trace("Store {} in zip.", ds);
if (ds == null)
return;
String dir = Dir.get(ds.getClass());
String entryName = "ILCD" + "/" + dir + "/" + ds.getUUID() + ".xml";
try {
Path entry = zip.getPath(entryName);
Path parent = entry.getParent();
if (parent != null && !Files.exists(parent))
Files.createDirectories(parent);
OutputStream os = Files.newOutputStream(entry);
binder.toStream(ds, os);
List<Path> list = getEntries(dir);
list.add(entry);
} catch (Exception e) {
throw new DataStoreException("Could not add file " + entryName, e);
}
}
@Override
public void put(Source source, File[] files)
throws DataStoreException {
log.trace("Store source {} with digital files", source);
put(source);
if (files == null || files.length == 0)
return;
try {
Path parent = zip.getPath("ILCD/external_docs");
if (parent != null && !Files.exists(parent))
Files.createDirectories(parent);
for (File file : files) {
Path entry = zip.getPath("ILCD/external_docs/" + file.getName());
Files.copy(file.toPath(), entry,
StandardCopyOption.REPLACE_EXISTING);
List<Path> list = getEntries("external_docs");
list.add(entry);
}
} catch (Exception e) {
throw new DataStoreException("Could not store digital files", e);
}
}
@Override
public InputStream getExternalDocument(String sourceId, String file)
throws DataStoreException {
log.trace("Get external document {}", file);
Path entry = findEntry("external_docs", file);
if (entry == null)
return null;
try {
return Files.newInputStream(entry);
} catch (Exception e) {
throw new DataStoreException("failed to open file " + file, e);
}
}
@Override
public <T> boolean delete(Class<T> type, String id)
throws DataStoreException {
throw new UnsupportedOperationException("delete in zips not supported");
}
<T> T unmarshal(Class<T> type, Path entry) throws DataStoreException {
try {
InputStream is = Files.newInputStream(entry);
T t = binder.fromStream(type, is);
return t;
} catch (Exception e) {
throw new DataStoreException("Cannot load " + type + " from entry "
+ entry, e);
}
}
private Path findEntry(String dir, String id) {
List<Path> list = entries.get(dir);
if (list == null)
return null;
for (Path entry : list) {
String name = entry.getFileName().toString();
if (name.contains(id))
return entry;
}
return null;
}
@Override
public <T> Iterator<T> iterator(Class<T> type) throws DataStoreException {
log.trace("create iterator for type {}", type);
return new ZipEntryIterator<>(this, type);
}
@Override
public <T> boolean contains(Class<T> type, String id)
throws DataStoreException {
return findEntry(Dir.get(type), id) != null;
}
@Override
public void close() throws DataStoreException {
log.trace("close zip store");
if (entries == null)
return;
entries = null;
try {
zip.close();
} catch (Exception e) {
throw new DataStoreException("Could not close ZipStore", e);
}
}
private class FileVisitor extends SimpleFileVisitor<Path> {
private Consumer<Path> fn;
public FileVisitor(Consumer<Path> fn) {
this.fn = fn;
}
@Override
public FileVisitResult visitFile(Path f, BasicFileAttributes atts)
throws IOException {
if (f == null || f.getParent() == null)
return FileVisitResult.CONTINUE;
fn.accept(f);
return FileVisitResult.CONTINUE;
}
}
}