| package org.apache.taverna.scufl2.ucfpackage; |
| /* |
| * |
| * 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. |
| * |
| */ |
| |
| |
| import static java.io.File.createTempFile; |
| import static java.util.logging.Level.INFO; |
| |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.PipedInputStream; |
| import java.io.PipedOutputStream; |
| import java.net.URI; |
| import java.nio.file.FileVisitResult; |
| import java.nio.file.FileVisitor; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.attribute.BasicFileAttributes; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.TransformerConfigurationException; |
| import javax.xml.transform.TransformerException; |
| import javax.xml.transform.TransformerFactory; |
| import javax.xml.transform.dom.DOMSource; |
| import javax.xml.transform.stream.StreamResult; |
| |
| import org.apache.taverna.robundle.Bundle; |
| import org.apache.taverna.robundle.Bundles; |
| import org.apache.taverna.robundle.manifest.Manifest; |
| import org.apache.taverna.robundle.manifest.PathMetadata; |
| import org.apache.taverna.robundle.utils.RecursiveDeleteVisitor; |
| import org.apache.taverna.robundle.utils.TemporaryFiles; |
| import org.w3c.dom.Document; |
| |
| |
| public class UCFPackage implements Cloneable { |
| private static Logger logger = Logger.getLogger(UCFPackage.class.getName()); |
| private static final URI VERSION_BASE = URI.create("http://ns.taverna.org.uk/2010/scufl2/"); |
| private static final String CONTAINER_XML = "META-INF/container.xml"; |
| public static final String MIME_BINARY = "application/octet-stream"; |
| public static final String MIME_TEXT_PLAIN = "text/plain"; |
| public static final String MIME_TEXT_XML = "text/xml"; |
| public static final String MIME_RDF = "application/rdf+xml"; |
| public static final String MIME_WORKFLOW_BUNDLE = "application/vnd.taverna.workflow-bundle"; |
| |
| private Bundle bundle; |
| |
| |
| public UCFPackage() throws IOException { |
| try { |
| bundle = Bundles.createBundle(); |
| //odfPackage = OdfPackage.create(); |
| bundle.getManifest().populateFromBundle(); |
| bundle.getManifest().writeAsODFManifest(); |
| } catch (IOException e) { |
| throw e; |
| } catch (Exception e) { |
| throw new IOException("Could not create empty UCF Package", e); |
| } |
| } |
| |
| public UCFPackage(File containerFile) throws IOException { |
| open(containerFile); |
| } |
| |
| protected void open(File containerFile) throws IOException { |
| bundle = Bundles.openBundleReadOnly(containerFile.toPath()); |
| } |
| |
| public UCFPackage(InputStream inputStream) throws IOException { |
| open(inputStream); |
| } |
| |
| protected UCFPackage(Bundle bundle) throws IOException { |
| this.bundle = bundle; |
| } |
| |
| protected void open(InputStream inputStream) throws IOException { |
| try { |
| Path bundlePath = TemporaryFiles.temporaryBundle(); |
| Files.copy(inputStream, bundlePath); |
| bundle = Bundles.openBundle(bundlePath); |
| bundle.setDeleteOnClose(true); |
| } catch (IOException e) { |
| throw e; |
| } catch (Exception e) { |
| throw new IOException( |
| "Could not load UCF Package from input stream", e); |
| } |
| } |
| |
| public String getPackageMediaType() { |
| try { |
| return Bundles.getMimeType(bundle); |
| } catch (IOException e) { |
| return MIME_WORKFLOW_BUNDLE; |
| } |
| } |
| |
| public void setPackageMediaType(String mediaType) { |
| try { |
| Bundles.setMimeType(bundle, mediaType); |
| } catch (IOException e) { |
| throw new RuntimeException("Can't set media type", e); |
| } |
| } |
| |
| public void save(File packageFile) throws IOException { |
| Path source = bundle.getSource(); |
| boolean deleteOnClose = bundle.isDeleteOnClose(); |
| bundle.setDeleteOnClose(false); |
| Bundles.closeAndSaveBundle(bundle, packageFile.toPath()); |
| // Re-open the original source (usually a tmpfile) |
| bundle = Bundles.openBundle(source); |
| bundle.setDeleteOnClose(deleteOnClose); |
| } |
| |
| |
| public void addResource(String stringValue, String path, String mediaType) |
| throws IOException { |
| Path bundlePath = writableBundlePath(path); |
| Bundles.setStringValue(bundlePath, stringValue); |
| Manifest manifest = bundle.getManifest(); |
| manifest.getAggregation(bundlePath).setMediatype(mediaType); |
| } |
| |
| public void addResource(byte[] bytesValue, String path, String mediaType) |
| throws IOException { |
| |
| Path bundlePath = writableBundlePath(path); |
| Files.write(bundlePath, bytesValue); |
| Manifest manifest = bundle.getManifest(); |
| manifest.getAggregation(bundlePath).setMediatype(mediaType); |
| } |
| |
| private Path writableBundlePath(String path) { |
| Path bundlePath = bundle.getRoot().resolve(path); |
| if (bundlePath.getParent() != null) { |
| try { |
| Files.createDirectories(bundlePath.getParent()); |
| } catch (IOException e) { |
| throw new RuntimeException("Could not create parent directories of " + path, e); |
| } |
| } |
| return bundlePath; |
| } |
| |
| @Deprecated |
| public void addResource(Document document, String path, String mediaType) |
| throws IOException { |
| |
| Path bundlePath = writableBundlePath(path); |
| TransformerFactory tFactory = TransformerFactory.newInstance(); |
| Transformer transformer; |
| try { |
| transformer = tFactory.newTransformer(); |
| } catch (TransformerConfigurationException e) { |
| throw new IOException("Can't create XML transformer to save " |
| + path, e); |
| } |
| |
| DOMSource source = new DOMSource(document); |
| try (OutputStream outStream = Files.newOutputStream(bundlePath)) { |
| StreamResult result = new StreamResult(outStream); |
| transformer.transform(source, result); |
| } catch (TransformerException e) { |
| throw new IOException("Can't save XML to " + path, e); |
| } |
| } |
| |
| public void addResource(InputStream inputStream, String path, |
| String mediaType) throws IOException { |
| Path bundlePath = writableBundlePath(path); |
| Files.copy(inputStream, bundlePath); |
| Manifest manifest = bundle.getManifest(); |
| manifest.getAggregation(bundlePath).setMediatype(mediaType); |
| } |
| |
| public void addResource(URI uri, String path, String mediaType) |
| throws IOException { |
| Path bundlePath = writableBundlePath(path); |
| Bundles.setReference(bundlePath, uri); |
| Manifest manifest = bundle.getManifest(); |
| manifest.getAggregation(bundlePath).setMediatype(mediaType); |
| } |
| |
| public String getResourceAsString(String path) throws IOException { |
| Path bundlePath = bundle.getRoot().resolve(path); |
| if (! Files.exists(bundlePath)) { |
| throw new FileNotFoundException("Can't find: " + bundlePath); |
| } |
| if (! Files.isRegularFile(bundlePath)) { |
| throw new IOException("Not a regular file: " + bundlePath); |
| } |
| return Bundles.getStringValue(bundlePath); |
| } |
| |
| public byte[] getResourceAsBytes(String path) throws IOException { |
| Path bundlePath = bundle.getRoot().resolve(path); |
| return Files.readAllBytes(bundlePath); |
| } |
| |
| public InputStream getResourceAsInputStream(String path) throws IOException { |
| Path bundlePath = bundle.getRoot().resolve(path); |
| if (! Files.isReadable(bundlePath)) { |
| throw new IOException("Can't read " + bundlePath); |
| } |
| return Files.newInputStream(bundlePath); |
| } |
| |
| public Map<String, ResourceEntry> listResources() { |
| return listResources("", false); |
| } |
| |
| public Map<String, ResourceEntry> listResources(String folderPath) { |
| return listResources(folderPath, false); |
| } |
| |
| protected Map<String, ResourceEntry> listResources(String folderPath, |
| final boolean recursive) { |
| final Path bundlePath = bundle.getRoot().resolve(folderPath); |
| final List<Path> reserved = Arrays.asList(bundle.getRoot().resolve("META-INF/"), |
| bundle.getRoot().resolve(".ro/"), |
| bundle.getRoot().resolve("mimetype") |
| ); |
| |
| final HashMap<String, ResourceEntry> content = new HashMap<String, ResourceEntry>(); |
| FileVisitor<Path> visitor = new FileVisitor<Path>() { |
| @Override |
| public FileVisitResult preVisitDirectory(Path dir, |
| BasicFileAttributes attrs) throws IOException { |
| if (reserved.contains(dir)) { |
| return FileVisitResult.SKIP_SUBTREE; |
| } |
| if (! dir.equals(bundlePath)) { |
| storePath(dir); |
| } |
| return recursive ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE; |
| } |
| |
| private void storePath(Path path) { |
| String pathStr = bundlePath.relativize(path).toString(); |
| content.put(pathStr, new ResourceEntry(path)); |
| } |
| |
| @Override |
| public FileVisitResult visitFile(Path file, |
| BasicFileAttributes attrs) throws IOException { |
| if (reserved.contains(file)) { |
| return FileVisitResult.CONTINUE; |
| } |
| storePath(file); |
| return FileVisitResult.CONTINUE; |
| } |
| |
| @Override |
| public FileVisitResult visitFileFailed(Path file, IOException exc) |
| throws IOException { |
| logger.log(Level.WARNING, "Could not visit " + file, exc); |
| return FileVisitResult.CONTINUE; |
| } |
| |
| @Override |
| public FileVisitResult postVisitDirectory(Path dir, IOException exc) |
| throws IOException { |
| return FileVisitResult.CONTINUE; |
| } |
| }; |
| |
| try { |
| Files.walkFileTree(bundlePath, visitor); |
| } catch (IOException e) { |
| throw new RuntimeException("Can't list resources of " + folderPath, e); |
| } |
| return content; |
| } |
| |
| public void removeResource(String path) { |
| Path bundlePath = bundle.getRoot().resolve(path); |
| try { |
| RecursiveDeleteVisitor.deleteRecursively(bundlePath); |
| } catch (IOException e) { |
| throw new RuntimeException("Could not delete " + path + " or its children", e); |
| } |
| } |
| |
| public class ResourceEntry { |
| |
| private Path path; |
| |
| public ResourceEntry(Path path) { |
| this.path = path; |
| } |
| |
| public String getPath() { |
| return path.toString().replaceFirst("^/", ""); |
| } |
| |
| public long getSize() { |
| try { |
| return Files.size(path); |
| } catch (IOException e) { |
| throw new RuntimeException("Can't determine size of " + path, e); |
| } |
| } |
| |
| public String getMediaType() { |
| try { |
| return bundle.getManifest().getAggregation(path).getMediatype(); |
| } catch (IOException e) { |
| throw new RuntimeException("Can't get media type for " + path, e); |
| } |
| } |
| |
| public boolean isFolder() { |
| return Files.isDirectory(path); |
| } |
| |
| public UCFPackage getUcfPackage() { |
| return UCFPackage.this; |
| } |
| |
| @Override |
| public String toString() { |
| return getPath(); |
| }; |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (! (obj instanceof ResourceEntry)) { |
| return false; |
| } |
| ResourceEntry other = (ResourceEntry) obj; |
| |
| if (! getUcfPackage().equals(other.getUcfPackage())) { |
| return false; |
| } |
| return path.equals(other.path); |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + getUcfPackage().hashCode(); |
| result = prime * result + ((path == null) ? 0 : path.hashCode()); |
| return result; |
| } |
| |
| public String getVersion() { |
| URI conformsTo; |
| try { |
| conformsTo = bundle.getManifest().getAggregation(path).getConformsTo(); |
| } catch (IOException e) { |
| throw new RuntimeException("Can't look up version for " + path, e); |
| } |
| if (conformsTo != null) { |
| URI version = VERSION_BASE.relativize(conformsTo); |
| if (!version.isAbsolute()) { |
| return version.toString(); |
| } |
| } |
| return null; |
| |
| } |
| } |
| |
| public Map<String, ResourceEntry> listAllResources() { |
| return listResources("/", true); |
| } |
| |
| // public void setRootFile(String path) { |
| // bundle.addRootFile(bundle.getPath(path)); |
| // } |
| |
| @SuppressWarnings("rawtypes") |
| public void setRootFile(String path, String version) { |
| // setRootFile(path); |
| if (version == null) { |
| return; |
| } |
| try { |
| PathMetadata metadata = bundle.getManifest().getAggregation(bundle.getPath(path)); |
| metadata.setConformsTo(VERSION_BASE.resolve(version)); |
| } catch (IOException e) { |
| throw new RuntimeException("Could not set root file: "+ path, e); |
| } |
| } |
| |
| // public List<ResourceEntry> getRootFiles() { |
| // List<ResourceEntry> files = new ArrayList<>(); |
| // for (Path path : bundle.getRootFiles()) { |
| // if (Files.exists(path)) { |
| // files.add(new ResourceEntry(path)); |
| // } |
| // } |
| // return files; |
| // } |
| |
| |
| public ResourceEntry getResourceEntry(String path) { |
| Path bundlePath = bundle.getRoot().resolve(path); |
| if (Files.exists(bundlePath)) { |
| return new ResourceEntry(bundlePath); |
| } |
| return null; |
| } |
| |
| // @SuppressWarnings("rawtypes") |
| //public void unsetRootFile(String path) { |
| //bundle.removeAsRootFile(bundle.getPath(path)); |
| //} |
| |
| public void save(OutputStream output) throws IOException { |
| Path source = bundle.getSource(); |
| boolean deleteOnClose = bundle.isDeleteOnClose(); |
| bundle.setDeleteOnClose(false); |
| Bundles.closeBundle(bundle); |
| Files.copy(source, output); |
| |
| // Re-open the original source (usually a tmpfile) |
| bundle = Bundles.openBundle(source); |
| bundle.setDeleteOnClose(deleteOnClose); |
| } |
| |
| public OutputStream addResourceUsingOutputStream(String path, |
| String mediaType) throws IOException { |
| if (path.equals(CONTAINER_XML)) { |
| throw new IllegalArgumentException("Can't add " + CONTAINER_XML |
| + " using OutputStream"); |
| // as we need to parse it after insertion |
| } |
| Path bundlePath = writableBundlePath(path); |
| return Files.newOutputStream(bundlePath); |
| } |
| |
| @Override |
| public UCFPackage clone() { |
| Path source = bundle.getSource(); |
| boolean deleteOnClose = bundle.isDeleteOnClose(); |
| bundle.setDeleteOnClose(false); |
| try { |
| Bundles.closeBundle(bundle); |
| } catch (IOException e) { |
| throw new RuntimeException("Could not save bundle to " + source, e); |
| } |
| |
| Bundle clonedBundle; |
| try { |
| clonedBundle = Bundles.openBundleReadOnly(source); |
| } catch (IOException e) { |
| throw new RuntimeException("Could not copy bundle from " + source, e); |
| } |
| |
| // Re-open the original source (usually a tmpfile) |
| try { |
| bundle = Bundles.openBundle(source); |
| bundle.setDeleteOnClose(deleteOnClose); |
| return new UCFPackage(clonedBundle); |
| } catch (IOException e) { |
| throw new RuntimeException("Could not re-open from " + source, e); |
| } |
| } |
| |
| private PipedInputStream copyToOutputStream( |
| final PipedOutputStream outputStream) throws IOException { |
| PipedInputStream inputStream = new PipedInputStream(outputStream); |
| new Thread("Cloning " + this) { |
| @Override |
| public void run() { |
| try { |
| try { |
| save(outputStream); |
| } finally { |
| outputStream.close(); |
| } |
| } catch (IOException e) { |
| logger.log(Level.INFO, "Could not save/close UCF package while cloning", e); |
| } |
| } |
| }.start(); |
| return inputStream; |
| } |
| |
| public String getRootFileVersion(String rootFile) { |
| return getResourceEntry(rootFile).getVersion(); |
| } |
| |
| } |