All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fcrepo.storage.ocfl.PersistencePaths Maven / Gradle / Ivy

/*
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree.
 */

package org.fcrepo.storage.ocfl;

import org.apache.commons.lang3.StringUtils;

/**
 * This class maps Fedora resources to locations on disk. It is based on this wiki:
 * https://wiki.lyrasis.org/display/FF/Design+-+Fedora+OCFL+Object+Structure
 *
 * @author pwinckles
 * @since 6.0.0
 */
public final class PersistencePaths {

    public static final String FCR_METADATA = "fcr:metadata";
    public static final String FCR_ACL = "fcr:acl";

    private static final String HEADER_DIR = ".fcrepo/";
    private static final String ROOT_PREFIX = "fcr-root";
    private static final String CONTAINER_PREFIX = "fcr-container";
    private static final String ACL_SUFFIX = "~fcr-acl";
    private static final String DESCRIPTION_SUFFIX = "~fcr-desc";
    private static final String RDF_EXTENSION = ".nt";
    private static final String JSON_EXTENSION = ".json";

    public static final String ROOT_HEADER_PATH = HEADER_DIR + ROOT_PREFIX + JSON_EXTENSION;

    private final String contentFilePath;
    private final String headerFilePath;

    private PersistencePaths(final String contentFilePath, final String headerFilePath) {
        this.contentFilePath = contentFilePath;
        this.headerFilePath = headerFilePath;
    }

    public String getContentFilePath() {
        return contentFilePath;
    }

    public String getHeaderFilePath() {
        return headerFilePath;
    }

    /**
     * Returns the path to the resourceId's header file. The rootResourceId is the id of the resource that's at the
     * root of the OCFL object. In the case of atomic resources the rootId and resourceId are one and the same. They
     * are only different for archival parts.
     *
     * @param rootResourceId the id of the resource at the root of the OCFL object
     * @param resourceId the id of the resource to get the header path for
     * @return path to header file
     */
    public static String headerPath(final String rootResourceId, final String resourceId) {
        final var info = analyze(rootResourceId, resourceId);
        return resolveHeaderPath(info);
    }

    /**
     * Returns the paths for a non-RDF resource. The rootResourceId is the id of the resource that's at the root of
     * the OCFL object. In the case of atomic resources the rootResourceId and resourceId are one and the same.
     * They are only different for archival parts.
     *
     * @param rootResourceId the id of the resource at the root of the OCFL object
     * @param resourceId the id of the non-rdf resource to get the paths for
     * @return paths
     */
    public static PersistencePaths nonRdfResource(final String rootResourceId, final String resourceId) {
        final var info = analyze(rootResourceId, resourceId);
        final var headerPath = resolveHeaderPath(info);
        final var contentPath = resolveContentPath(false, info);
        return new PersistencePaths(contentPath, headerPath);
    }

    /**
     * Returns the paths for RDF resources. It should NOT be used for ACL resources. The resourceRootId is the id
     * of the resource that's at the root of the OCFL object. In the case of atomic resources the rootResourceId and
     * resourceId are one and the same. They are only different for archival parts.
     *
     * @param rootResourceId the id of the resource at the root of the OCFL object
     * @param resourceId the id of the rdf resource to get the paths for
     * @return paths
     */
    public static PersistencePaths rdfResource(final String rootResourceId, final String resourceId) {
        final var info = analyze(rootResourceId, resourceId);
        if (info.isAcl) {
            throw new IllegalArgumentException("You must use aclContentPath() for ACL resources.");
        }
        final var headerPath = resolveHeaderPath(info);
        final var contentPath = resolveContentPath(!info.isDescription, info);
        return new PersistencePaths(contentPath, headerPath);
    }

    /**
     * Returns the paths for ACL resources. The rootResourceId is the id of the resource that's at the root of the
     * OCFL object. In the case of atomic resources the rootResourceId and resourceId are one and the same. They
     * are only different for archival parts.
     *
     * @param describesRdfResource indicates if the acl is associated to a rdf resource
     * @param rootResourceId the id of the resource at the root of the OCFL object
     * @param resourceId the id of the acl resource to get the paths for
     * @return paths
     */
    public static PersistencePaths aclResource(final boolean describesRdfResource,
                                               final String rootResourceId, final String resourceId) {
        final var info = analyze(rootResourceId, resourceId);
        if (!info.isAcl) {
            throw new IllegalArgumentException("This function should only be called for ACL resources.");
        }
        final var headerPath = resolveHeaderPath(info);
        final var contentPath = resolveContentPath(describesRdfResource, info);
        return new PersistencePaths(contentPath, headerPath);
    }

    /**
     * Returns true if the path is a resource header file.
     *
     * @param path file path
     * @return true if it's a resource header file
     */
    public static boolean isHeaderFile(final String path) {
        return path.startsWith(HEADER_DIR);
    }

    private static String resolveContentPath(final boolean isContainer, final IdInfo info) {
        if (info.isRoot) {
            if (isContainer) {
                if (info.isAcl) {
                    return CONTAINER_PREFIX + ACL_SUFFIX + RDF_EXTENSION;
                } else {
                    return CONTAINER_PREFIX + RDF_EXTENSION;
                }
            }
        }

        final var pathBuilder = new StringBuilder(info.relativeId);

        if (info.isDescription) {
            pathBuilder.append(DESCRIPTION_SUFFIX).append(RDF_EXTENSION);
        } else if (isContainer) {
            if (info.isAcl) {
                pathBuilder.append("/").append(CONTAINER_PREFIX).append(ACL_SUFFIX).append(RDF_EXTENSION);
            } else {
                pathBuilder.append("/").append(CONTAINER_PREFIX).append(RDF_EXTENSION);
            }
        } else if (info.isAcl) {
            pathBuilder.append(ACL_SUFFIX).append(RDF_EXTENSION);
        }

        return pathBuilder.toString();
    }

    private static String resolveHeaderPath(final IdInfo info) {
        String path;

        if (info.isRoot) {
            path = ROOT_PREFIX;
        } else {
            path = info.relativeId;
        }

        if (info.isAcl) {
            path += ACL_SUFFIX;
        } else if (info.isDescription) {
            path += DESCRIPTION_SUFFIX;
        }

        return headerPath(path);
    }

    private static String headerPath(final String path) {
        return HEADER_DIR + path + JSON_EXTENSION;
    }

    private static IdInfo analyze(final String rootResourceId, final String resourceId) {
        if (rootResourceId.equals(resourceId)) {
            return IdInfo.root(resourceId);
        }

        if (!resourceId.startsWith(rootResourceId)) {
            throw new IllegalArgumentException(String.format("The resources %s and %s are unrelated",
                    resourceId, rootResourceId));
        }

        final var relative = resourceId.substring(rootResourceId.length() + 1);

        if (relative.equals(FCR_ACL)) {
            return IdInfo.rootAcl(StringUtils.substringBeforeLast(resourceId, "/"));
        } else if (relative.equals(FCR_METADATA)) {
            return IdInfo.rootDescription(StringUtils.substringBeforeLast(resourceId, "/"));
        }

        final var info = IdInfo.regular(resourceId, relative);

        if (relative.endsWith(FCR_ACL)) {
            info.isAcl = true;
            info.relativeId = StringUtils.substringBeforeLast(relative, "/");
        } else if (relative.endsWith(FCR_METADATA)) {
            info.isDescription = true;
            info.relativeId = StringUtils.substringBeforeLast(relative, "/");
        }

        return info;
    }

    private static class IdInfo {

        String resourceId;
        String relativeId;
        boolean isRoot;
        boolean isAcl;
        boolean isDescription;

        static IdInfo root(final String resourceId) {
            final var lastPart =  resourceId.substring(resourceId.lastIndexOf('/') + 1);
            final var info = new IdInfo(resourceId, lastPart);
            info.isRoot = true;
            return info;
        }

        static IdInfo rootAcl(final String resourceId) {
            final var info = root(resourceId);
            info.isAcl = true;
            return info;
        }

        static IdInfo rootDescription(final String resourceId) {
            final var info = root(resourceId);
            info.isDescription = true;
            return info;
        }

        static IdInfo regular(final String resourceId, final String relativeId) {
            return new IdInfo(resourceId, relativeId);
        }

        IdInfo(final String resourceId, final String relativeId) {
            this.resourceId = resourceId;
            this.relativeId = relativeId;
            this.isRoot = false;
            this.isAcl = false;
            this.isDescription = false;
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy