org.apache.taverna.robundle.manifest.Manifest.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.taverna.robundle.manifest.Manifest.java

Source

package org.apache.taverna.robundle.manifest;

/*
 * 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 com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_EMPTY_BEANS;
import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_EMPTY_JSON_ARRAYS;
import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_NULL_MAP_VALUES;
import static java.nio.file.FileVisitResult.CONTINUE;
import static java.nio.file.FileVisitResult.SKIP_SUBTREE;
import static java.nio.file.Files.createDirectories;
import static java.nio.file.Files.getLastModifiedTime;
import static java.nio.file.Files.isDirectory;
import static java.nio.file.Files.newBufferedWriter;
import static java.nio.file.Files.walkFileTree;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
import static java.nio.file.attribute.FileTime.fromMillis;
import static org.apache.taverna.robundle.Bundles.uriToBundlePath;

import java.io.IOException;
import java.io.Writer;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import org.apache.taverna.robundle.Bundle;
import org.apache.taverna.robundle.manifest.combine.CombineManifest;
import org.apache.taverna.robundle.manifest.odf.ODFManifest;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.ObjectMapper;

@JsonPropertyOrder(value = { "@context", "id", "manifest", "createdOn", "createdBy", "createdOn", "authoredOn",
        "authoredBy", "retrievedFrom", "retrievedOn", "retrievedBy", "history", "aggregates", "annotations",
        "@graph" })
public class Manifest {
    public abstract class FileTimeMixin {
        @Override
        @JsonValue
        public abstract String toString();
    }

    public abstract class PathMixin {
        @Override
        @JsonValue
        public abstract String toString();
    }

    private static Logger logger = Logger.getLogger(Manifest.class.getCanonicalName());

    private static final String MANIFEST_JSON = "manifest.json";

    private static final String META_INF = "/META-INF";

    private static final String MIMETYPE = "/mimetype";
    private static final String RO = "/.ro";
    private static URI ROOT = URI.create("/");

    public static FileTime now() {
        return fromMillis(new GregorianCalendar().getTimeInMillis());
    }

    protected static Path withSlash(Path dir) {
        if (dir == null)
            return null;
        if (isDirectory(dir)) {
            Path fname = dir.getFileName();
            if (fname == null)
                return dir;
            String fnameStr = fname.toString();
            if (fnameStr.endsWith("/"))
                return dir;
            return dir.resolveSibling(fnameStr + "/");
        }
        return dir;
    }

    private Map<URI, PathMetadata> aggregates = new LinkedHashMap<>();
    private List<PathAnnotation> annotations = new ArrayList<>();
    private List<Agent> authoredBy = new ArrayList<>();
    private FileTime authoredOn;
    private Bundle bundle;
    private Agent createdBy = null;
    private FileTime createdOn = now();
    private URI retrievedFrom = null;
    private Agent retrievedBy = null;
    private FileTime retrievedOn = null;
    private List<String> graph;
    private List<Path> history = new ArrayList<>();
    private URI id = URI.create("/");
    private List<Path> manifest = new ArrayList<>();

    public Manifest(Bundle bundle) {
        this.bundle = bundle;
    }

    public List<PathMetadata> getAggregates() {
        return new ArrayList<>(aggregates.values());
    }

    public PathMetadata getAggregation(Path file) {
        URI fileUri = file.toUri();
        return getAggregation(fileUri);
    }

    public PathMetadata getAggregation(URI uri) {
        uri = relativeToBundleRoot(uri);
        PathMetadata metadata = aggregates.get(uri);
        if (metadata == null) {
            metadata = new PathMetadata();
            if (!uri.isAbsolute() && uri.getFragment() == null) {
                Path path = uriToBundlePath(bundle, uri);
                metadata.setFile(path);
                metadata.setMediatype(guessMediaType(path));
            } else {
                metadata.setUri(uri);
            }
            aggregates.put(uri, metadata);
        }
        return metadata;
    }

    public List<PathAnnotation> getAnnotations() {
        return annotations;
    }

    public List<Agent> getAuthoredBy() {
        return authoredBy;
    }

    public FileTime getAuthoredOn() {
        return authoredOn;
    }

    @JsonIgnore
    public URI getBaseURI() {
        return getBundle().getRoot().toUri();
    }

    @JsonIgnore
    public Bundle getBundle() {
        return bundle;
    }

    @JsonProperty(value = "@context")
    public List<Object> getContext() {
        ArrayList<Object> context = new ArrayList<>();
        // HashMap<Object, Object> map = new HashMap<>();
        // map.put("@base", getBaseURI());
        // context.add(map);
        context.add(URI.create("https://w3id.org/bundle/context"));
        return context;
    }

    public Agent getCreatedBy() {
        return createdBy;
    }

    public FileTime getCreatedOn() {
        return createdOn;
    }

    public URI getRetrievedFrom() {
        return retrievedFrom;
    }

    public Agent getRetrievedBy() {
        return retrievedBy;
    }

    public FileTime getRetrievedOn() {
        return retrievedOn;
    }

    public List<String> getGraph() {
        return graph;
    }

    public List<Path> getHistory() {
        return history;
    }

    public URI getId() {
        return id;
    }

    public List<Path> getManifest() {
        return manifest;
    }

    /**
     * Guess media type based on extension
     * 
     * @see http://wf4ever.github.io/ro/bundle/#media-types
     * 
     * @param file
     *            A Path to a file
     * @return media-type, e.g. <code>application/xml</code> or
     *         <code>text/plain; charset="utf-8"</code>
     */
    public String guessMediaType(Path file) {
        if (file.getFileName() == null)
            return null;
        String filename = file.getFileName().toString().toLowerCase(Locale.ENGLISH);
        if (filename.endsWith(".txt"))
            return "text/plain; charset=\"utf-8\"";
        if (filename.endsWith(".ttl"))
            return "text/turtle; charset=\"utf-8\"";
        if (filename.endsWith(".rdf") || filename.endsWith(".owl"))
            return "application/rdf+xml";
        if (filename.endsWith(".json"))
            return "application/json";
        if (filename.endsWith(".jsonld"))
            return "application/ld+json";
        if (filename.endsWith(".xml"))
            return "application/xml";

        // A few extra, common ones

        if (filename.endsWith(".png"))
            return "image/png";
        if (filename.endsWith(".svg"))
            return "image/svg+xml";
        if (filename.endsWith(".jpg") || filename.endsWith(".jpeg"))
            return "image/jpeg";
        if (filename.endsWith(".pdf"))
            return "application/pdf";
        return "application/octet-stream";
    }

    public void populateFromBundle() throws IOException {
        final Set<Path> potentiallyEmptyFolders = new LinkedHashSet<>();

        final Set<URI> existingAggregationsToPrune = new HashSet<>(aggregates.keySet());

        walkFileTree(bundle.getRoot(), new SimpleFileVisitor<Path>() {
            @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(withSlash(dir));
                    metadata.setFolder(withSlash(dir.getParent()));
                    metadata.setProxy();
                    metadata.setCreatedOn(getLastModifiedTime(dir));
                    potentiallyEmptyFolders.remove(withSlash(dir.getParent()));
                    return CONTINUE;
                }
                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(withSlash(dir));
                potentiallyEmptyFolders.remove(withSlash(dir.getParent()));
                return CONTINUE;
            }

            @SuppressWarnings("deprecation")
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                potentiallyEmptyFolders.remove(withSlash(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);
                }
                metadata.setFile(file);
                if (metadata.getMediatype() == null)
                    // Don't override if already set
                    metadata.setMediatype(guessMediaType(file));
                metadata.setFolder(withSlash(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<PathMetadata> 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<PathAnnotation> annotations) {
        this.annotations = annotations;
    }

    public void setAuthoredBy(List<Agent> 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;
    }

    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<String> graph) {
        this.graph = graph;
    }

    public void setHistory(List<Path> 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<Path> manifest) {
        this.manifest = manifest;
    }

    public void writeAsCombineManifest() throws IOException {
        new CombineManifest(this).createManifestXML();
    }

    /**
     * Write as an RO Bundle JSON-LD manifest
     * 
     * @return The path of the written manifest (e.g. ".ro/manifest.json")
     * @throws IOException
     */
    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();
        om.addMixInAnnotations(Path.class, PathMixin.class);
        om.addMixInAnnotations(FileTime.class, FileTimeMixin.class);
        om.enable(INDENT_OUTPUT);
        om.disable(WRITE_EMPTY_JSON_ARRAYS);
        om.disable(FAIL_ON_EMPTY_BEANS);
        om.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);
        }
        return jsonld;
    }

    /**
     * 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();
    }
}