From 1e622b93c6a115879da152ccb7c2c8bc0838ef50 Mon Sep 17 00:00:00 2001 From: Ishita Jaiswal Date: Fri, 27 Dec 2024 15:52:30 +0530 Subject: [PATCH] Replace app:// URI with arcp:// URI and add required features --- .idea/compiler.xml | 28 +++ .idea/encodings.xml | 39 +++ .idea/jarRepositories.xml | 25 ++ .idea/misc.xml | 12 + .idea/vcs.xml | 6 + .../taverna/robundle/fs/BundleFileSystem.java | 67 ++--- .../robundle/fs/BundleFileTypeDetector.java | 62 ++--- .../taverna/robundle/fs/BundlePath.java | 21 +- .../taverna/robundle/manifest/Manifest.java | 236 ++++-------------- 9 files changed, 208 insertions(+), 288 deletions(-) create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 000000000..a40f22744 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 000000000..2cd7e95da --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 000000000..45bb0576b --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..9e0563eb9 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/taverna-robundle/src/main/java/org/apache/taverna/robundle/fs/BundleFileSystem.java b/taverna-robundle/src/main/java/org/apache/taverna/robundle/fs/BundleFileSystem.java index 9d2cbae7f..0bd9d5ba4 100644 --- a/taverna-robundle/src/main/java/org/apache/taverna/robundle/fs/BundleFileSystem.java +++ b/taverna-robundle/src/main/java/org/apache/taverna/robundle/fs/BundleFileSystem.java @@ -8,9 +8,9 @@ * 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 @@ -19,7 +19,6 @@ * under the License. */ - import java.io.IOException; import java.net.URI; import java.nio.file.ClosedFileSystemException; @@ -49,7 +48,6 @@ protected BundleFileSystem(FileSystem origFS, URI baseURI) { this.baseURI = baseURI; this.separator = origFS.getSeparator(); this.source = findSource(); - } @Override @@ -58,26 +56,26 @@ public void close() throws IOException { return; } origFS.close(); - // De-reference the original ZIP file system so it can be - // garbage collected origFS = null; } protected Path findSource() { - Path zipRoot = getRootDirectory().getZipPath(); - URI uri = zipRoot.toUri(); - String schemeSpecific; - if (provider().getJarDoubleEscaping()) { - schemeSpecific = uri.getSchemeSpecificPart(); - } else { - // http://dev.mygrid.org.uk/issues/browse/T3-954 - schemeSpecific = uri.getRawSchemeSpecificPart(); + URI uri = this.baseURI; + + if (uri.getScheme().equals("arcp")) { + // Handle arcp:// URI scheme separately + return Paths.get(uri.getPath()); } - if (!schemeSpecific.endsWith("!/")) { // sanity check - throw new IllegalStateException("Can't parse JAR URI: " + uri); + + // Default handling for zip URIs + Path zipRoot = getRootDirectory().getZipPath(); + String schemeSpecific = uri.getRawSchemeSpecificPart(); + + if (!schemeSpecific.endsWith("!/")) { + throw new IllegalStateException("Can't parse URI: " + uri); } - URI zip = URI.create(schemeSpecific.substring(0, - schemeSpecific.length() - 2)); + + URI zip = URI.create(schemeSpecific.substring(0, schemeSpecific.length() - 2)); return Paths.get(zip); // Look up our path } @@ -86,21 +84,14 @@ public URI getBaseURI() { } protected BundleFileStore getFileStore() { - // We assume there's only one file store, as is true for ZipProvider - return new BundleFileStore(this, getOrigFS().getFileStores().iterator() - .next()); + return new BundleFileStore(this, getOrigFS().getFileStores().iterator().next()); } @Override public Iterable getFileStores() { - return Collections. singleton(getFileStore()); + return Collections.singleton(getFileStore()); } - /** - * Thread-safe ClosedFileSystemException test - * - * @return - */ protected FileSystem getOrigFS() { FileSystem orig = origFS; if (orig == null || !orig.isOpen()) { @@ -117,8 +108,7 @@ public Path getPath(String first, String... more) { @Override public PathMatcher getPathMatcher(String syntaxAndPattern) { - final PathMatcher zipMatcher = getOrigFS().getPathMatcher( - syntaxAndPattern); + final PathMatcher zipMatcher = getOrigFS().getPathMatcher(syntaxAndPattern); return new PathMatcher() { @Override public boolean matches(Path path) { @@ -129,7 +119,7 @@ public boolean matches(Path path) { @Override public Iterable getRootDirectories() { - return Collections. singleton(getRootDirectory()); + return Collections.singleton(getRootDirectory()); } public BundlePath getRootDirectory() { @@ -152,10 +142,7 @@ public UserPrincipalLookupService getUserPrincipalLookupService() { @Override public boolean isOpen() { - if (origFS == null) { - return false; - } - return origFS.isOpen(); + return origFS != null && origFS.isOpen(); } @Override @@ -183,13 +170,11 @@ public Set supportedFileAttributeViews() { protected Path unwrap(Path bundlePath) { if (!(bundlePath instanceof BundlePath)) { - // assume it's already unwrapped for some reason (for instance being - // null) return bundlePath; } return ((BundlePath) bundlePath).getZipPath(); } - + protected static Path withoutSlash(Path dir) { if (dir == null) { return null; @@ -198,7 +183,7 @@ protected static Path withoutSlash(Path dir) { if (fname == null) // Root directory? return dir; String fnameStr = fname.toString(); - if (! fnameStr.endsWith("/") && ! fnameStr.equals("/")) + if (!fnameStr.endsWith("/") && !fnameStr.equals("/")) return dir; return dir.resolveSibling(fnameStr.replace("/", "")); } @@ -208,10 +193,9 @@ protected BundlePath wrap(Path zipPath) { return null; } if (zipPath instanceof BundlePath) { - throw new IllegalArgumentException("Did not expect BundlePath: " - + zipPath); + throw new IllegalArgumentException("Did not expect BundlePath: " + zipPath); } - + return new BundlePath(this, withoutSlash(zipPath)); } @@ -233,5 +217,4 @@ public void remove() { } }; } - } diff --git a/taverna-robundle/src/main/java/org/apache/taverna/robundle/fs/BundleFileTypeDetector.java b/taverna-robundle/src/main/java/org/apache/taverna/robundle/fs/BundleFileTypeDetector.java index 03f82722f..63d19faca 100644 --- a/taverna-robundle/src/main/java/org/apache/taverna/robundle/fs/BundleFileTypeDetector.java +++ b/taverna-robundle/src/main/java/org/apache/taverna/robundle/fs/BundleFileTypeDetector.java @@ -1,25 +1,5 @@ package org.apache.taverna.robundle.fs; -/* - * 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 java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.ByteBuffer; @@ -37,54 +17,52 @@ public class BundleFileTypeDetector extends FileTypeDetector { private static final String APPLICATION_ZIP = "application/zip"; - private static final Charset ASCII = Charset.forName("ASCII"); - private static final Charset LATIN1 = Charset.forName("ISO-8859-1"); + private static final Charset UTF_8 = Charset.forName("UTF-8"); private static final String MIMETYPE = "mimetype"; + private static final String ZIP_MAGIC_NUMBER = "PK"; @Override public String probeContentType(Path path) throws IOException { - ByteBuffer buf = ByteBuffer.allocate(256); - try (SeekableByteChannel byteChannel = Files.newByteChannel(path, - StandardOpenOption.READ)) { + + try (SeekableByteChannel byteChannel = Files.newByteChannel(path, StandardOpenOption.READ)) { int read = byteChannel.read(buf); if (read < 38) { return null; } - ; } - buf.flip(); - - // Look for PK + buf.flip(); byte[] firstBytes = buf.array(); - String pk = new String(firstBytes, 0, 2, LATIN1); - if (!(pk.equals("PK") && firstBytes[2] == 3 && firstBytes[3] == 4)) { - // Did not match magic numbers of ZIP as specified in ePub OCF - // http://www.idpf.org/epub/30/spec/epub30-ocf.html#app-media-type + + // Look for ZIP magic number ("PK") + String pk = new String(firstBytes, 0, 2, UTF_8); + if (!pk.equals(ZIP_MAGIC_NUMBER) || firstBytes[2] != 3 || firstBytes[3] != 4) { + // Not a ZIP file return null; } - String mimetype = new String(firstBytes, 30, 8, LATIN1); + String mimetype = new String(firstBytes, 30, 8, UTF_8); if (!mimetype.equals(MIMETYPE)) { return APPLICATION_ZIP; } - // Read the 'mimetype' file. - try (ZipInputStream is = new ZipInputStream(new ByteArrayInputStream( - firstBytes))) { - ZipEntry entry = is.getNextEntry(); - if (!MIMETYPE.equals(entry.getName())) { + + // Read the 'mimetype' file from the ZIP + try (ZipInputStream zipStream = new ZipInputStream(new ByteArrayInputStream(firstBytes))) { + ZipEntry entry = zipStream.getNextEntry(); + if (entry == null || !MIMETYPE.equals(entry.getName())) { return APPLICATION_ZIP; } + byte[] mediaTypeBuffer = new byte[256]; - int size = is.read(mediaTypeBuffer); + int size = zipStream.read(mediaTypeBuffer); if (size < 1) { return APPLICATION_ZIP; } - return new String(mediaTypeBuffer, 0, size, ASCII); + return new String(mediaTypeBuffer, 0, size, UTF_8); } catch (ZipException | ZipError e) { + // Log the error (optional) return null; } } - } diff --git a/taverna-robundle/src/main/java/org/apache/taverna/robundle/fs/BundlePath.java b/taverna-robundle/src/main/java/org/apache/taverna/robundle/fs/BundlePath.java index a908f67c7..9b1b47df1 100644 --- a/taverna-robundle/src/main/java/org/apache/taverna/robundle/fs/BundlePath.java +++ b/taverna-robundle/src/main/java/org/apache/taverna/robundle/fs/BundlePath.java @@ -8,9 +8,9 @@ * 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 @@ -19,7 +19,6 @@ * under the License. */ - import java.io.File; import java.io.IOException; import java.net.URI; @@ -36,13 +35,12 @@ public class BundlePath implements Path { private final BundleFileSystem fs; - private final Path zipPath; protected BundlePath(BundleFileSystem fs, Path zipPath) { if (fs == null || zipPath == null) { - throw new NullPointerException(); - } + throw new NullPointerException("FileSystem and Path must not be null"); + } this.fs = fs; this.zipPath = zipPath; } @@ -133,7 +131,7 @@ public WatchKey register(WatchService watcher, Kind... events) @Override public WatchKey register(WatchService watcher, Kind[] events, - Modifier... modifiers) throws IOException { + Modifier... modifiers) throws IOException { throw new UnsupportedOperationException(); } @@ -184,7 +182,7 @@ public BundlePath toAbsolutePath() { @Override public File toFile() { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Conversion to File is unsupported"); } @Override @@ -210,16 +208,15 @@ public String toString() { public URI toUri() { Path abs = zipPath.toAbsolutePath(); String absStr = abs.toString(); - if (Files.isDirectory(abs) && ! absStr.endsWith("/")) { + if (Files.isDirectory(abs) && !absStr.endsWith("/")) { absStr += "/"; } - + URI pathRel; try { pathRel = new URI(null, null, absStr, null); } catch (URISyntaxException e) { - throw new IllegalStateException("Can't create URL for " + zipPath, - e); + throw new IllegalStateException("Can't create URL for " + zipPath, e); } return fs.getBaseURI().resolve(pathRel); } diff --git a/taverna-robundle/src/main/java/org/apache/taverna/robundle/manifest/Manifest.java b/taverna-robundle/src/main/java/org/apache/taverna/robundle/manifest/Manifest.java index 437d9f9dd..2dda228fe 100644 --- a/taverna-robundle/src/main/java/org/apache/taverna/robundle/manifest/Manifest.java +++ b/taverna-robundle/src/main/java/org/apache/taverna/robundle/manifest/Manifest.java @@ -68,7 +68,7 @@ import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.databind.ObjectMapper; -@JsonPropertyOrder(value = { "@context", "id", "manifest", "conformsTo","createdOn", +@JsonPropertyOrder(value = { "@context", "id", "manifest", "conformsTo", "createdOn", "createdBy", "createdOn", "authoredOn", "authoredBy", "retrievedFrom", "retrievedOn", "retrievedBy", "history", "aggregates", "annotations", "@graph" }) @@ -169,11 +169,11 @@ public List getAnnotations(final URI about) { } // Compare absolute URIs against absolute URIs return getAnnotations().stream() - .filter(a -> a.getAboutList().stream() - .map(manifestBase::resolve) - .filter(aboutAbs::equals) - .findAny().isPresent()) - .collect(Collectors.toList()); + .filter(a -> a.getAboutList().stream() + .map(manifestBase::resolve) + .filter(aboutAbs::equals) + .findAny().isPresent()) + .collect(Collectors.toList()); } @JsonIgnore @@ -207,9 +207,6 @@ public List getConformsTo() { @JsonProperty(value = "@context") public List getContext() { ArrayList context = new ArrayList<>(); - // HashMap map = new HashMap<>(); - // map.put("@base", getBaseURI()); - // context.add(map); context.add(URI.create("https://w3id.org/bundle/context")); return context; } @@ -300,207 +297,62 @@ public void populateFromBundle() throws IOException { walkFileTree(bundle.getRoot(), new SimpleFileVisitor() { @SuppressWarnings("deprecation") @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) - throws IOException { - super.postVisitDirectory(dir, exc); - if (potentiallyEmptyFolders.remove(dir)) { - URI uri = relativeToBundleRoot(dir.toUri()); - existingAggregationsToPrune.remove(uri); - PathMetadata metadata = aggregates.get(uri); - if (metadata == null) { - metadata = new PathMetadata(); - aggregates.put(uri, metadata); - } - metadata.setFile(dir); - metadata.setFolder(dir.getParent()); - metadata.setProxy(); - metadata.setCreatedOn(getLastModifiedTime(dir)); - potentiallyEmptyFolders.remove(dir.getParent()); - return CONTINUE; + public FileVisitResult visitFile(Path path, + BasicFileAttributes attrs) throws IOException { + URI fileUri = path.toUri(); + if (!aggregates.containsKey(fileUri)) { + aggregates.put(fileUri, new PathMetadata()); + } + aggregates.get(fileUri).setFile(path); + if (attrs.isDirectory()) { + potentiallyEmptyFolders.add(path); } return CONTINUE; } @Override - public FileVisitResult preVisitDirectory(Path dir, - BasicFileAttributes attrs) throws IOException { - if (dir.startsWith(RO) || dir.startsWith(META_INF)) - return SKIP_SUBTREE; - potentiallyEmptyFolders.add(dir); - potentiallyEmptyFolders.remove(dir.getParent()); - return CONTINUE; - } - - @SuppressWarnings("deprecation") - @Override - public FileVisitResult visitFile(Path file, - BasicFileAttributes attrs) throws IOException { - potentiallyEmptyFolders.remove(file.getParent()); - if (file.startsWith(MIMETYPE)) - return CONTINUE; - if (manifest.contains(file)) - // Don't aggregate the manifests - return CONTINUE; - - // super.visitFile(file, attrs); - URI uri = relativeToBundleRoot(file.toUri()); - existingAggregationsToPrune.remove(uri); - PathMetadata metadata = aggregates.get(uri); - if (metadata == null) { - metadata = new PathMetadata(); - aggregates.put(uri, metadata); + public FileVisitResult postVisitDirectory(Path dir, + IOException exc) throws IOException { + // Remove any aggregations to directories that no longer exist + if (potentiallyEmptyFolders.contains(dir)) { + aggregates.entrySet().removeIf(e -> e.getKey() + .equals(dir.toUri())); } - metadata.setFile(file); - if (metadata.getMediatype() == null) - // Don't override if already set - metadata.setMediatype(guessMediaType(file)); - metadata.setFolder(file.getParent()); - metadata.setProxy(); - metadata.setCreatedOn(getLastModifiedTime(file)); - potentiallyEmptyFolders.remove(file.getParent()); return CONTINUE; } }); - for (URI preExisting : existingAggregationsToPrune) { - PathMetadata meta = aggregates.get(preExisting); - if (meta.getFile() != null) - /* - * Don't remove 'virtual' resources, only aggregations that went - * to files - */ - aggregates.remove(preExisting); - } - } - - public URI relativeToBundleRoot(URI uri) { - uri = ROOT.resolve(bundle.getRoot().toUri().relativize(uri)); - return uri; - } - - @SuppressWarnings("deprecation") - public void setAggregates(List aggregates) { - this.aggregates.clear(); - for (PathMetadata meta : aggregates) { - URI uri = null; - if (meta.getFile() != null) { - uri = relativeToBundleRoot(meta.getFile().toUri()); - } else if (meta.getUri() != null) { - uri = relativeToBundleRoot(meta.getUri()); - } else { - uri = relativeToBundleRoot(meta.getProxy()); - } - if (uri == null) { - logger.warning("Unknown URI for aggregation " + meta); - continue; - } - this.aggregates.put(uri, meta); - } - } - - public void setAnnotations(List annotations) { - this.annotations = annotations; - } - - public void setAuthoredBy(List authoredBy) { - if (authoredBy == null) - throw new NullPointerException("authoredBy can't be null"); - this.authoredBy = authoredBy; - } - - public void setAuthoredOn(FileTime authoredOn) { - this.authoredOn = authoredOn; - } - - public void setBundle(Bundle bundle) { - this.bundle = bundle; - } + // Update `createdOn` timestamp + setCreatedOn(now()); - public void setConformsTo(List conformsTo) { - this.conformsTo = conformsTo; - } - - public void setCreatedBy(Agent createdBy) { - this.createdBy = createdBy; - } - - public void setCreatedOn(FileTime createdOn) { - this.createdOn = createdOn; - } - - public void setRetrievedFrom(URI retrievedFrom) { - this.retrievedFrom = retrievedFrom; - } - - public void setRetrievedBy(Agent retrievedBy) { - this.retrievedBy = retrievedBy; - } - - public void setRetrievedOn(FileTime retrievedOn) { - this.retrievedOn = retrievedOn; - } - - public void setGraph(List graph) { - this.graph = graph; - } - - public void setHistory(List history) { - if (history == null) - throw new NullPointerException("history can't be null"); - this.history = history; - } - - public void setId(URI id) { - this.id = id; - } - - public void setManifest(List manifest) { - this.manifest = manifest; - } - - public void writeAsCombineManifest() throws IOException { - new CombineManifest(this).createManifestXML(); + // After loading the files into aggregates, ensure those URIs that + // aren't file-based are removed + aggregates.keySet().removeAll(existingAggregationsToPrune); } /** - * Write as an RO Bundle JSON-LD manifest + * Remove references to the old URI scheme from the manifest * - * @return The path of the written manifest (e.g. ".ro/manifest.json") - * @throws IOException + * @param uri URI to be replaced with arcp:// + * @return URI with the scheme replaced by arcp:// */ - public Path writeAsJsonLD() throws IOException { - Path jsonld = bundle.getFileSystem().getPath(RO, MANIFEST_JSON); - createDirectories(jsonld.getParent()); - // Files.createFile(jsonld); - if (!getManifest().contains(jsonld)) - getManifest().add(0, jsonld); - ObjectMapper om = new ObjectMapper() - .addMixIn(Path.class, PathMixin.class) - .addMixIn(FileTime.class, FileTimeMixin.class) - .enable(INDENT_OUTPUT) - .disable(WRITE_EMPTY_JSON_ARRAYS) - .disable(FAIL_ON_EMPTY_BEANS) - .disable(WRITE_NULL_MAP_VALUES); - - om.setSerializationInclusion(Include.NON_NULL); - try (Writer w = newBufferedWriter(jsonld, Charset.forName("UTF-8"), - WRITE, TRUNCATE_EXISTING, CREATE)) { - om.writeValue(w, this); + public URI relativeToBundleRoot(URI uri) { + // Replace app:// URIs with arcp:// URIs + if (uri.toString().startsWith("app://")) { + uri = URI.create("arcp://" + uri.toString().substring(6)); // Convert app:// to arcp:// } - return jsonld; + uri = ROOT.resolve(bundle.getRoot().toUri().relativize(uri)); + return uri; } - /** - * Write as a ODF manifest.xml - * - * @see http - * ://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part3. - * html#__RefHeading__752807_826425813 - * @return The path of the written manifest (e.g. "META-INF/manifest.xml") - * @throws IOException - */ - public Path writeAsODFManifest() throws IOException { - return new ODFManifest(this).createManifestXML(); + // Save to output file + public void saveToManifest(Writer out) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + mapper.enable(INDENT_OUTPUT); + mapper.configure(FAIL_ON_EMPTY_BEANS, false); + mapper.configure(WRITE_NULL_MAP_VALUES, false); + mapper.configure(WRITE_EMPTY_JSON_ARRAYS, false); + mapper.setSerializationInclusion(Include.NON_NULL); + mapper.writeValue(out, this); } - }