org.fcrepo.kernel.FedoraResource.java Source code

Java tutorial

Introduction

Here is the source code for org.fcrepo.kernel.FedoraResource.java

Source

/**
 * Copyright 2013 DuraSpace, Inc.
 *
 * Licensed 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.
 */
package org.fcrepo.kernel;

import com.google.common.collect.Iterables;
import com.hp.hpl.jena.graph.Triple;
import com.hp.hpl.jena.query.Dataset;
import com.hp.hpl.jena.query.DatasetFactory;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.update.UpdateRequest;
import org.fcrepo.jcr.FedoraJcrTypes;
import org.fcrepo.kernel.rdf.GraphSubjects;
import org.fcrepo.kernel.rdf.JcrRdfTools;
import org.fcrepo.kernel.utils.JcrPropertyStatementListener;
import org.fcrepo.kernel.utils.iterators.DifferencingIterator;
import org.fcrepo.kernel.utils.iterators.RdfAdder;
import org.fcrepo.kernel.utils.iterators.RdfRemover;
import org.fcrepo.kernel.utils.iterators.RdfStream;
import org.modeshape.jcr.api.JcrTools;
import org.slf4j.Logger;

import javax.jcr.Node;
import javax.jcr.PropertyIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.version.VersionHistory;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Set;

import static com.google.common.collect.ImmutableSet.copyOf;
import static com.hp.hpl.jena.rdf.model.ModelFactory.createDefaultModel;
import static com.hp.hpl.jena.update.UpdateAction.execute;
import static com.hp.hpl.jena.update.UpdateFactory.create;
import static org.apache.commons.codec.digest.DigestUtils.shaHex;
import static org.fcrepo.kernel.rdf.GraphProperties.PROBLEMS_MODEL_NAME;
import static org.fcrepo.kernel.rdf.GraphProperties.URI_SYMBOL;
import static org.fcrepo.kernel.services.ServiceHelpers.getObjectSize;
import static org.fcrepo.kernel.utils.FedoraTypesUtils.getBaseVersion;
import static org.fcrepo.kernel.utils.FedoraTypesUtils.getVersionHistory;
import static org.fcrepo.kernel.utils.FedoraTypesUtils.isFedoraResource;
import static org.fcrepo.kernel.utils.FedoraTypesUtils.isFrozen;
import static org.fcrepo.kernel.utils.FedoraTypesUtils.map;
import static org.fcrepo.kernel.utils.FedoraTypesUtils.nodetype2name;
import static org.modeshape.jcr.api.JcrConstants.JCR_CONTENT;
import static org.modeshape.jcr.api.JcrConstants.NT_FOLDER;
import static org.slf4j.LoggerFactory.getLogger;

/**
 * Common behaviors across FedoraObject and Datastream types; also used
 * when the exact type of an object is irrelevant
 */
public class FedoraResource extends JcrTools implements FedoraJcrTypes {

    private static final Logger LOGGER = getLogger(FedoraResource.class);

    protected Node node;

    /**
     * Construct a FedoraObject without a backing JCR Node
     */
    public FedoraResource() {
        super(false);
        node = null;
    }

    /**
     * Construct a FedoraObject from an existing JCR Node
     * @param node an existing JCR node to treat as an fcrepo object
     */
    public FedoraResource(final Node node) {
        this();
        this.node = node;
    }

    /**
     * Create or find a FedoraObject at the given path
     * @param session the JCR session to use to retrieve the object
     * @param path the absolute path to the object
     * @throws RepositoryException
     */
    public FedoraResource(final Session session, final String path, final String nodeType)
            throws RepositoryException {
        this();
        this.node = findOrCreateNode(session, path, NT_FOLDER, nodeType);

        if (!hasMixin(node) && !isFrozen(node)) {
            node.addMixin(FEDORA_RESOURCE);
        }

        if (node.isNew()) {
            node.setProperty(JCR_LASTMODIFIED, Calendar.getInstance());
        }
    }

    /**
     * Is the given node a frozen node?
     *
     * @param node
     * @return
     * @throws RepositoryException
     */
    public static boolean isFrozen(final Node node) throws RepositoryException {
        return isFrozen.apply(node);
    }

    /**
     * Is the given node a Fedora resource
     * (because it has a fedora:resource mixin)?
     *
     * @param node
     * @return
     * @throws RepositoryException
     */
    public static boolean hasMixin(final Node node) throws RepositoryException {
        return isFedoraResource.apply(node);
    }

    /**
     * Does the resource have a jcr:content child node?
     * @return
     * @throws RepositoryException
     */
    public boolean hasContent() throws RepositoryException {
        return node.hasNode(JCR_CONTENT);
    }

    /**
     * @return The JCR node that backs this object.
     */
    public Node getNode() {
        return node;
    }

    /**
     * Get the path to the JCR node
     * @return
     * @throws RepositoryException
     */
    public String getPath() throws RepositoryException {
        return node.getPath();
    }

    /**
     * Get the date this datastream was created
     * @return
     * @throws RepositoryException
     */
    public Date getCreatedDate() throws RepositoryException {
        if (node.hasProperty(JCR_CREATED)) {
            return new Date(node.getProperty(JCR_CREATED).getDate().getTimeInMillis());
        } else {
            LOGGER.debug("Node {} does not have a createdDate", node);
            return null;
        }
    }

    /**
     * Get the date this datastream was last modified
     * @return
     * @throws RepositoryException
     */
    public Date getLastModifiedDate() throws RepositoryException {
        if (node.hasProperty(JCR_LASTMODIFIED)) {
            return new Date(node.getProperty(JCR_LASTMODIFIED).getDate().getTimeInMillis());
        } else {
            LOGGER.debug("Could not get last modified date property for node {}", node);
        }

        final Date createdDate = getCreatedDate();

        if (createdDate != null) {
            LOGGER.trace("Using created date for last modified date for node {}", node);
            return createdDate;
        }

        return null;
    }

    /**
     * Get the total size of this object and its datastreams
     * @return size in bytes
     * @throws RepositoryException
     */
    public long getSize() throws RepositoryException {
        return getObjectSize(node);
    }

    /**
     * Get the mixins this object uses
     * @return a collection of mixin names
     * @throws javax.jcr.RepositoryException
     */
    public Collection<String> getModels() throws RepositoryException {
        if (isFrozen.apply(node)) {
            Collection<String> results = new ArrayList<String>();
            PropertyIterator pIt = node.getProperties(FROZEN_MIXIN_TYPES);
            while (pIt.hasNext()) {
                results.add(pIt.nextProperty().getString());
            }
            return results;
        } else {
            return map(node.getMixinNodeTypes(), nodetype2name);
        }
    }

    /**
     * Update the properties Dataset with a SPARQL Update query. The updated
     * properties may be serialized to the JCR store.
     *
     * After applying the statement, clients SHOULD check the result
     * of #getDatasetProblems, which may include problems when attempting to
     * serialize the data to JCR.
     *
     * @param subjects
     * @param sparqlUpdateStatement
     * @throws RepositoryException
     */
    public Dataset updatePropertiesDataset(final GraphSubjects subjects, final String sparqlUpdateStatement)
            throws RepositoryException {
        final Dataset dataset = getPropertiesDataset(subjects);
        final UpdateRequest request = create(sparqlUpdateStatement, dataset.getContext().getAsString(URI_SYMBOL));
        dataset.getDefaultModel().setNsPrefixes(request.getPrefixMapping());
        execute(request, dataset);
        return dataset;
    }

    /**
     * Return the JCR properties of this object as a Jena {@link Dataset}
     *
     * @param subjects
     * @param offset
     * @param limit
     * @return
     * @throws RepositoryException
     */
    public Dataset getPropertiesDataset(final GraphSubjects graphSubjects, final int offset, final int limit)
            throws RepositoryException {

        final JcrRdfTools jcrRdfTools = JcrRdfTools.withContext(graphSubjects, getNode().getSession());

        final RdfStream propertiesStream = jcrRdfTools.getJcrTriples(getNode());

        propertiesStream.concat(jcrRdfTools.getTreeTriples(getNode()));

        final Dataset dataset = DatasetFactory.create(propertiesStream.limit(limit).skip(offset).asModel());

        final Model problemsModel = createDefaultModel();

        final JcrPropertyStatementListener listener = JcrPropertyStatementListener.getListener(graphSubjects,
                node.getSession(), problemsModel);

        dataset.getDefaultModel().register(listener);

        dataset.addNamedModel(PROBLEMS_MODEL_NAME, problemsModel);

        dataset.getContext().set(URI_SYMBOL, graphSubjects.getGraphSubject(getNode()));

        return dataset;
    }

    /**
     * Return the JCR properties of this object as a Jena {@link Dataset}
     * @return
     * @throws RepositoryException
     */
    public Dataset getPropertiesDataset(final GraphSubjects subjects) throws RepositoryException {
        return getPropertiesDataset(subjects, 0, -1);
    }

    /**
     * Return the JCR properties of this object as an {@link RdfStream}
     * @return
     * @throws RepositoryException
     */
    public RdfStream getTriples(final GraphSubjects graphSubjects) throws RepositoryException {

        final JcrRdfTools jcrRdfTools = JcrRdfTools.withContext(graphSubjects, getNode().getSession());

        return jcrRdfTools.getJcrTriples(getNode());
    }

    /**
     * Return the JCR properties of this object as an {@link RdfStream}
     * @return
     * @throws RepositoryException
     */
    public RdfStream getHierarchyTriples(final GraphSubjects graphSubjects) throws RepositoryException {

        final JcrRdfTools jcrRdfTools = JcrRdfTools.withContext(graphSubjects, getNode().getSession());

        return jcrRdfTools.getTreeTriples(getNode());
    }

    /**
     * Serialize the JCR versions information as an RDF dataset
     * @param subjects
     * @return
     * @throws RepositoryException
     */
    public RdfStream getVersionTriples(final GraphSubjects graphSubjects) throws RepositoryException {
        return JcrRdfTools.withContext(graphSubjects, node.getSession()).getVersionTriples(node);
    }

    /**
     * Tag the current version of the Node with a version label that
     * can be retrieved by name later.
     *
     * @param label
     * @throws RepositoryException
     */
    public void addVersionLabel(final String label) throws RepositoryException {
        final VersionHistory versionHistory = getVersionHistory(node);
        versionHistory.addVersionLabel(getBaseVersion(node).getName(), label, true);
    }

    /**
     * Check if a resource was created in this session
     * @return
     */
    public boolean isNew() {
        return node.isNew();
    }

    /**
     * Replace the properties of this object with the properties from the given
     * model
     *
     * @param subjects
     * @param inputModel
     * @return
     * @throws Exception
     */
    public RdfStream replaceProperties(final GraphSubjects graphSubjects, final Model inputModel) throws Exception {
        final RdfStream originalTriples = getTriples(graphSubjects);

        final RdfStream replacementStream = RdfStream.fromModel(inputModel);

        final Set<Triple> replacementTriples = copyOf(replacementStream.iterator());

        final DifferencingIterator<Triple> differencer = new DifferencingIterator<>(replacementTriples,
                originalTriples);

        new RdfRemover(graphSubjects, getNode().getSession(), replacementStream.withThisContext(differencer))
                .consume();

        new RdfAdder(graphSubjects, getNode().getSession(),
                replacementStream.withThisContext(differencer.notCommon())).consume();

        return replacementStream.withThisContext(Iterables.concat(differencer.common(), differencer.notCommon()));
    }

    /**
     * Construct an ETag value from the last modified date and path. JCR has a
     * mix:etag type, but it only takes into account binary properties. We
     * actually want whole-object etag data. TODO : construct and store an ETag
     * value on object modify
     *
     * @return
     * @throws RepositoryException
     */
    public String getEtagValue() throws RepositoryException {
        final Date lastModifiedDate = getLastModifiedDate();

        if (lastModifiedDate != null) {
            return shaHex(node.getPath() + lastModifiedDate.toString());
        } else {
            return "";
        }
    }
}