blob: 991f8be39926158165008fa489dc836e2639cc46 [file] [log] [blame]
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.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
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.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import org.apache.taverna.robundle.manifest.odf.ODFJaxb;
import org.apache.taverna.scufl2.ucfpackage.impl.odfdom.pkg.OdfPackage;
import org.apache.taverna.scufl2.ucfpackage.impl.odfdom.pkg.manifest.OdfFileEntry;
import org.apache.taverna.robundle.xml.odf.container.Container;
import org.apache.taverna.robundle.xml.odf.container.Container.RootFiles;
import org.apache.taverna.robundle.xml.odf.container.ObjectFactory;
import org.apache.taverna.robundle.xml.odf.container.RootFile;
import org.w3c.dom.Document;
public class UCFPackage extends ODFJaxb implements Cloneable {
private static Logger logger = Logger.getLogger(UCFPackage.class.getName());
private static final String CONTAINER_XML = "META-INF/container.xml";
private static final Charset UTF_8 = Charset.forName("utf-8");
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_EPUB = "application/epub+zip";
public static final String MIME_WORKFLOW_BUNDLE = "application/vnd.taverna.workflow-bundle";
public static final String MIME_DATA_BUNDLE = "application/vnd.taverna.data-bundle";
public static final String MIME_WORKFLOW_RUN_BUNDLE = "application/vnd.taverna.workflow-run-bundle";
public static final String MIME_SERVICE_BUNDLE = "application/vnd.taverna.service-bundle";
private static Charset ASCII = Charset.forName("ascii");
private OdfPackage odfPackage;
private static JAXBContext jaxbContext;
private JAXBElement<Container> containerXml;
private boolean createdContainerXml = false;
private static ObjectFactory containerFactory = new ObjectFactory();
public UCFPackage() throws IOException {
try {
odfPackage = OdfPackage.create();
parseContainerXML();
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException("Could not create empty UCF Package", e);
}
// odfPackage.setMediaType(MIME_EPUB);
}
public UCFPackage(File containerFile) throws IOException {
open(containerFile);
}
protected void open(File containerFile) throws IOException {
try (BufferedInputStream stream = new BufferedInputStream(
new FileInputStream(containerFile))) {
open(stream);
}
}
public UCFPackage(InputStream inputStream) throws IOException {
open(inputStream);
}
protected void open(InputStream inputStream) throws IOException {
try {
odfPackage = OdfPackage.loadPackage(inputStream);
parseContainerXML();
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException(
"Could not load UCF Package from input stream", e);
}
}
@SuppressWarnings("unchecked")
protected void parseContainerXML() throws IOException {
createdContainerXml = false;
InputStream containerStream = getResourceAsInputStream(CONTAINER_XML);
if (containerStream == null) {
// Make an empty containerXml
Container container = containerFactory.createContainer();
containerXml = containerFactory.createContainer(container);
createdContainerXml = true;
return;
}
try {
Unmarshaller unMarshaller = createUnMarshaller();
containerXml = (JAXBElement<Container>) unMarshaller
.unmarshal(containerStream);
} catch (JAXBException e) {
throw new IOException("Could not parse " + CONTAINER_XML, e);
}
}
public String getPackageMediaType() {
return odfPackage.getMediaType();
}
public void setPackageMediaType(String mediaType) {
if (mediaType == null || !mediaType.contains("/"))
throw new IllegalArgumentException("Invalid media type "
+ mediaType);
if (!ASCII.newEncoder().canEncode(mediaType))
throw new IllegalArgumentException("Media type must be ASCII: "
+ mediaType);
odfPackage.setMediaType(mediaType);
}
public void save(File packageFile) throws IOException {
File tempFile = createTempFile("." + packageFile.getName(), ".tmp",
packageFile.getCanonicalFile().getParentFile());
prepareAndSave(tempFile);
boolean renamed = tempFile.renameTo(packageFile);
if (!renamed && packageFile.exists() && tempFile.exists()) {
// Could happen on Windows
if (!packageFile.delete())
// Could have been permission problem
throw new IOException("Could not delete existing "
+ packageFile);
renamed = tempFile.renameTo(packageFile);
}
if (!renamed)
throw new IOException("Could not rename temp file " + tempFile
+ " to " + packageFile);
}
protected void prepareAndSave(File tempFile) throws IOException {
if (getPackageMediaType() == null)
throw new IllegalStateException("Package media type must be set");
// Write using temp file, and do rename in the end
try {
prepareContainerXML();
odfPackage.save(tempFile);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException("Could not save bundle to " + tempFile, e);
} finally {
odfPackage.close();
}
try {
open(tempFile);
} catch (Exception e) {
throw new IOException("Could not reload package from " + tempFile,
e);
}
}
protected void prepareContainerXML() throws IOException {
if (containerXml == null || createdContainerXml
&& containerXml.getValue().getRootFilesOrAny() == null)
return;
/* Check if we should prune <rootFiles> */
Iterator<Object> iterator = containerXml.getValue().getRootFilesOrAny()
.iterator();
boolean foundAlready = false;
while (iterator.hasNext()) {
Object anyOrRoot = iterator.next();
if (!(anyOrRoot instanceof JAXBElement))
continue;
@SuppressWarnings("rawtypes")
JAXBElement elem = (JAXBElement) anyOrRoot;
if (!elem.getDeclaredType().equals(RootFiles.class))
continue;
RootFiles rootFiles = (RootFiles) elem.getValue();
if (foundAlready
|| (rootFiles.getOtherAttributes().isEmpty() && rootFiles
.getAnyOrRootFile().isEmpty())) {
// Delete it!
System.err.println("Deleting unneccessary <rootFiles>");
iterator.remove();
}
foundAlready = true;
}
Marshaller marshaller;
try {
marshaller = createMarshaller();
// XMLStreamWriter xmlStreamWriter = XMLOutputFactory
// .newInstance().createXMLStreamWriter(outStream);
// xmlStreamWriter.setDefaultNamespace(containerElem.getName()
// .getNamespaceURI());
//
// xmlStreamWriter.setPrefix("dsig",
// "http://www.w3.org/2000/09/xmldsig#");
// xmlStreamWriter.setPrefix("xmlenc",
// "http://www.w3.org/2001/04/xmlenc#");
try (OutputStream outStream = odfPackage
.insertOutputStream(CONTAINER_XML)) {
// FIXME: Set namespace prefixes and default namespace
marshaller.setProperty("jaxb.formatted.output", true);
// TODO: Ensure using default namespace
marshaller.marshal(containerXml, outStream);
}
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException("Could not parse " + CONTAINER_XML, e);
}
}
public void addResource(String stringValue, String path, String mediaType)
throws IOException {
try {
odfPackage.insert(stringValue.getBytes(UTF_8), path, mediaType);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException("Could not add " + path, e);
}
parseContainerXML();
}
public void addResource(byte[] bytesValue, String path, String mediaType)
throws IOException {
try {
odfPackage.insert(bytesValue, path, mediaType);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException("Could not add " + path, e);
}
if (path.equals(CONTAINER_XML))
parseContainerXML();
}
public void addResource(Document document, String path, String mediaType)
throws IOException {
try {
odfPackage.insert(document, path, mediaType);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException("Could not add " + path, e);
}
if (path.equals(CONTAINER_XML))
parseContainerXML();
}
public void addResource(InputStream inputStream, String path,
String mediaType) throws IOException {
try {
odfPackage.insert(inputStream, path, mediaType);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException("Could not add " + path, e);
}
if (path.equals(CONTAINER_XML))
parseContainerXML();
}
public void addResource(URI uri, String path, String mediaType)
throws IOException {
try {
odfPackage.insert(uri, path, mediaType);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException("Could not add " + path, e);
}
if (path.equals(CONTAINER_XML))
parseContainerXML();
}
public String getResourceAsString(String path) throws IOException {
try {
return new String(odfPackage.getBytes(path), UTF_8);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException("Could not get " + path, e);
}
}
public byte[] getResourceAsBytes(String path) throws IOException {
try {
return odfPackage.getBytes(path);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException("Could not get " + path, e);
}
}
public InputStream getResourceAsInputStream(String path) throws IOException {
try {
return odfPackage.getInputStream(path);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException("Could not get " + path, e);
}
}
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,
boolean recursive) {
if (!folderPath.isEmpty() && !folderPath.endsWith("/"))
folderPath = folderPath + "/";
HashMap<String, ResourceEntry> content = new HashMap<>();
for (Entry<String, OdfFileEntry> entry : odfPackage
.getManifestEntries().entrySet()) {
String entryPath = entry.getKey();
if (!entryPath.startsWith(folderPath))
continue;
String subPath = entryPath.substring(folderPath.length(),
entryPath.length());
if (subPath.isEmpty())
// The folder itself
continue;
int firstSlash = subPath.indexOf("/");
if (!recursive && firstSlash > -1
&& firstSlash < subPath.length() - 1)
/*
* Children of a folder (note that we'll include the folder
* itself which ends in /)
*/
continue;
content.put(subPath, new ResourceEntry(entry.getValue()));
}
return content;
}
public void removeResource(String path) {
if (!odfPackage.contains(path))
return;
if (path.endsWith("/"))
for (ResourceEntry childEntry : listResources(path).values())
removeResource(childEntry.getPath());
odfPackage.remove(path);
}
public class ResourceEntry {
private final String path;
private final long size;
private String mediaType;
private String version;
protected ResourceEntry(OdfFileEntry odfEntry) {
path = odfEntry.getPath();
size = odfEntry.getSize();
mediaType = odfEntry.getMediaType();
version = odfEntry.getVersion();
}
public String getPath() {
return path;
}
public long getSize() {
return size;
}
public String getMediaType() {
return mediaType;
}
public boolean isFolder() {
return path.endsWith("/");
}
public UCFPackage getUcfPackage() {
return UCFPackage.this;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ResourceEntry))
return false;
ResourceEntry other = (ResourceEntry) obj;
if (!getUcfPackage().equals(other.getUcfPackage()))
return false;
return getPath().equals(other.getPath());
}
@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() {
return version;
}
}
public Map<String, ResourceEntry> listAllResources() {
return listResources("", true);
}
public void setRootFile(String path) {
setRootFile(path, null);
}
@SuppressWarnings("rawtypes")
public void setRootFile(String path, String version) {
ResourceEntry rootFile = getResourceEntry(path);
if (rootFile == null)
throw new IllegalArgumentException("Unknown resource: " + path);
odfPackage.getManifestEntries().get(path).setVersion(version);
Container container = containerXml.getValue();
RootFiles rootFiles = getRootFiles(container);
String mediaType = rootFile.getMediaType();
boolean foundExisting = false;
// Check any existing files for matching path/mime type
Iterator<Object> anyOrRootIt = rootFiles.getAnyOrRootFile().iterator();
while (anyOrRootIt.hasNext()) {
Object anyOrRoot = anyOrRootIt.next();
if (anyOrRoot instanceof JAXBElement)
anyOrRoot = ((JAXBElement) anyOrRoot).getValue();
if (!(anyOrRoot instanceof RootFile))
continue;
RootFile rootFileElem = (RootFile) anyOrRoot;
if (!rootFileElem.getFullPath().equals(path)
&& !rootFileElem.getMediaType().equals(mediaType))
// Different path and media type - ignore
continue;
if (foundExisting) {
// Duplicate path/media type, we'll remove it
anyOrRootIt.remove();
continue;
}
rootFileElem.setFullPath(rootFile.getPath());
if (mediaType != null)
rootFileElem.setMediaType(mediaType);
foundExisting = true;
}
if (!foundExisting) {
RootFile rootFileElem = containerFactory.createRootFile();
rootFileElem.setFullPath(rootFile.getPath());
rootFileElem.setMediaType(mediaType);
rootFiles.getAnyOrRootFile().add(
containerFactory
.createContainerRootFilesRootFile(rootFileElem));
// rootFiles.getAnyOrRootFile().add(rootFileElem);
}
}
protected RootFiles getRootFiles(Container container) {
for (Object o : container.getRootFilesOrAny()) {
if (o instanceof JAXBElement) {
@SuppressWarnings("rawtypes")
JAXBElement jaxbElement = (JAXBElement) o;
o = jaxbElement.getValue();
}
if (o instanceof RootFiles)
return (RootFiles) o;
}
// Not found - add it
RootFiles rootFiles = containerFactory.createContainerRootFiles();
container.getRootFilesOrAny().add(
containerFactory.createContainerRootFiles(rootFiles));
return rootFiles;
}
@SuppressWarnings("rawtypes")
public List<ResourceEntry> getRootFiles() {
ArrayList<UCFPackage.ResourceEntry> rootFiles = new ArrayList<>();
if (containerXml == null)
return rootFiles;
RootFiles rootFilesElem = getRootFiles(containerXml.getValue());
for (Object anyOrRoot : rootFilesElem.getAnyOrRootFile()) {
if (anyOrRoot instanceof JAXBElement)
anyOrRoot = ((JAXBElement) anyOrRoot).getValue();
if (!(anyOrRoot instanceof RootFile))
continue;
RootFile rf = (RootFile) anyOrRoot;
ResourceEntry entry = getResourceEntry(rf.getFullPath());
if (rf.getMediaType() != null
&& rf.getMediaType() != entry.mediaType)
// Override the mime type in the returned entry
entry.mediaType = rf.getMediaType();
rootFiles.add(entry);
}
return rootFiles;
}
public ResourceEntry getResourceEntry(String path) {
OdfFileEntry odfFileEntry = odfPackage.getManifestEntries().get(path);
if (odfFileEntry == null)
return null;
return new ResourceEntry(odfFileEntry);
}
@SuppressWarnings("rawtypes")
public void unsetRootFile(String path) {
Container container = containerXml.getValue();
RootFiles rootFiles = getRootFiles(container);
Iterator<Object> anyOrRootIt = rootFiles.getAnyOrRootFile().iterator();
while (anyOrRootIt.hasNext()) {
Object anyOrRoot = anyOrRootIt.next();
if (anyOrRoot instanceof JAXBElement)
anyOrRoot = ((JAXBElement) anyOrRoot).getValue();
if (!(anyOrRoot instanceof RootFile))
continue;
RootFile rootFileElem = (RootFile) anyOrRoot;
if (rootFileElem.getFullPath().equals(path))
anyOrRootIt.remove();
}
}
protected JAXBElement<Container> getContainerXML() {
return containerXml;
}
public void save(OutputStream output) throws IOException {
File tempFile = createTempFile("ucfpackage", ".tmp");
prepareAndSave(tempFile);
// Copy file to the output
// Note - Should use IOUtils, but we're trying to avoid external dependencies
try (InputStream inStream = new FileInputStream(tempFile)) {
byte[] buffer = new byte[8192];
int n = 0;
do {
output.write(buffer, 0, n);
n = inStream.read(buffer);
} while (n > -1);
} finally {
tempFile.delete();
}
}
public OutputStream addResourceUsingOutputStream(String path,
String mediaType) throws IOException {
if (path.equals(CONTAINER_XML))
// as we need to parse it after insertion, this must fail
throw new IllegalArgumentException("Can't add " + CONTAINER_XML
+ " using OutputStream");
try {
return odfPackage.insertOutputStream(path, mediaType);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException("Could not add " + path, e);
}
}
@Override
public UCFPackage clone() {
final PipedOutputStream outputStream = new PipedOutputStream();
try {
try (PipedInputStream inputStream = copyToOutputStream(outputStream)) {
return new UCFPackage(inputStream);
}
} catch (IOException e) {
throw new RuntimeException("Could not clone UCFPackage", 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(INFO,
"Could not save/close UCF package while cloning", e);
}
}
}.start();
return inputStream;
}
public String getRootFileVersion(String rootFile) {
return getResourceEntry(rootFile).getVersion();
}
}