org.matonto.catalog.impl.SimpleCatalogManager.java Source code

Java tutorial

Introduction

Here is the source code for org.matonto.catalog.impl.SimpleCatalogManager.java

Source

package org.matonto.catalog.impl;

/*-
 * #%L
 * org.matonto.catalog.impl
 * $Id:$
 * $HeadURL:$
 * %%
 * Copyright (C) 2016 iNovex Information Systems, Inc.
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

import aQute.bnd.annotation.component.Activate;
import aQute.bnd.annotation.component.Component;
import aQute.bnd.annotation.component.ConfigurationPolicy;
import aQute.bnd.annotation.component.Modified;
import aQute.bnd.annotation.component.Reference;
import aQute.bnd.annotation.metatype.Configurable;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.matonto.catalog.api.CatalogManager;
import org.matonto.catalog.api.Conflict;
import org.matonto.catalog.api.Difference;
import org.matonto.catalog.api.PaginatedSearchParams;
import org.matonto.catalog.api.PaginatedSearchResults;
import org.matonto.catalog.api.builder.DistributionConfig;
import org.matonto.catalog.api.builder.RecordConfig;
import org.matonto.catalog.api.ontologies.mcat.Branch;
import org.matonto.catalog.api.ontologies.mcat.BranchFactory;
import org.matonto.catalog.api.ontologies.mcat.Catalog;
import org.matonto.catalog.api.ontologies.mcat.CatalogFactory;
import org.matonto.catalog.api.ontologies.mcat.Commit;
import org.matonto.catalog.api.ontologies.mcat.CommitFactory;
import org.matonto.catalog.api.ontologies.mcat.Distribution;
import org.matonto.catalog.api.ontologies.mcat.DistributionFactory;
import org.matonto.catalog.api.ontologies.mcat.InProgressCommit;
import org.matonto.catalog.api.ontologies.mcat.InProgressCommitFactory;
import org.matonto.catalog.api.ontologies.mcat.Record;
import org.matonto.catalog.api.ontologies.mcat.RecordFactory;
import org.matonto.catalog.api.ontologies.mcat.Revision;
import org.matonto.catalog.api.ontologies.mcat.RevisionFactory;
import org.matonto.catalog.api.ontologies.mcat.Tag;
import org.matonto.catalog.api.ontologies.mcat.UnversionedRecord;
import org.matonto.catalog.api.ontologies.mcat.UnversionedRecordFactory;
import org.matonto.catalog.api.ontologies.mcat.Version;
import org.matonto.catalog.api.ontologies.mcat.VersionFactory;
import org.matonto.catalog.api.ontologies.mcat.VersionedRDFRecord;
import org.matonto.catalog.api.ontologies.mcat.VersionedRDFRecordFactory;
import org.matonto.catalog.api.ontologies.mcat.VersionedRecord;
import org.matonto.catalog.api.ontologies.mcat.VersionedRecordFactory;
import org.matonto.catalog.config.CatalogConfig;
import org.matonto.catalog.util.SearchResults;
import org.matonto.exception.MatOntoException;
import org.matonto.jaas.api.ontologies.usermanagement.User;
import org.matonto.jaas.api.ontologies.usermanagement.UserFactory;
import org.matonto.ontologies.provo.Activity;
import org.matonto.ontologies.provo.Entity;
import org.matonto.persistence.utils.Bindings;
import org.matonto.query.TupleQueryResult;
import org.matonto.query.api.Binding;
import org.matonto.query.api.BindingSet;
import org.matonto.query.api.TupleQuery;
import org.matonto.rdf.api.IRI;
import org.matonto.rdf.api.Model;
import org.matonto.rdf.api.ModelFactory;
import org.matonto.rdf.api.Resource;
import org.matonto.rdf.api.Statement;
import org.matonto.rdf.api.Value;
import org.matonto.rdf.api.ValueFactory;
import org.matonto.rdf.orm.OrmFactory;
import org.matonto.rdf.orm.Thing;
import org.matonto.repository.api.Repository;
import org.matonto.repository.api.RepositoryConnection;
import org.matonto.repository.base.RepositoryResult;
import org.matonto.repository.exception.RepositoryException;
import org.openrdf.model.vocabulary.DCTERMS;
import org.openrdf.model.vocabulary.RDF;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.security.InvalidParameterException;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component(configurationPolicy = ConfigurationPolicy.require, designateFactory = CatalogConfig.class, name = SimpleCatalogManager.COMPONENT_NAME)
public class SimpleCatalogManager implements CatalogManager {

    static final String COMPONENT_NAME = "org.matonto.catalog.api.CatalogManager";
    private static final Logger log = Logger.getLogger(SimpleCatalogManager.class);
    private Repository repository;
    private ValueFactory vf;
    private ModelFactory mf;
    private CatalogFactory catalogFactory;
    private RecordFactory recordFactory;
    private DistributionFactory distributionFactory;
    private BranchFactory branchFactory;
    private UserFactory userFactory;
    private InProgressCommitFactory inProgressCommitFactory;
    private CommitFactory commitFactory;
    private RevisionFactory revisionFactory;
    private VersionedRDFRecordFactory versionedRDFRecordFactory;
    private VersionedRecordFactory versionedRecordFactory;
    private UnversionedRecordFactory unversionedRecordFactory;
    private VersionFactory versionFactory;
    private Resource distributedCatalogIRI;
    private Resource localCatalogIRI;
    private Map<Resource, String> sortingOptions = new HashMap<>();

    public SimpleCatalogManager() {
    }

    @Reference(name = "repository")
    protected void setRepository(Repository repository) {
        this.repository = repository;
    }

    @Reference
    protected void setValueFactory(ValueFactory valueFactory) {
        vf = valueFactory;
    }

    @Reference
    protected void setModelFactory(ModelFactory modelFactory) {
        mf = modelFactory;
    }

    @Reference
    protected void setCatalogFactory(CatalogFactory catalogFactory) {
        this.catalogFactory = catalogFactory;
    }

    @Reference
    protected void setRecordFactory(RecordFactory recordFactory) {
        this.recordFactory = recordFactory;
    }

    @Reference
    protected void setDistributionFactory(DistributionFactory distributionFactory) {
        this.distributionFactory = distributionFactory;
    }

    @Reference
    protected void setBranchFactory(BranchFactory branchFactory) {
        this.branchFactory = branchFactory;
    }

    @Reference
    protected void setUserFactory(UserFactory userFactory) {
        this.userFactory = userFactory;
    }

    @Reference
    protected void setInProgressCommitFactory(InProgressCommitFactory inProgressCommitFactory) {
        this.inProgressCommitFactory = inProgressCommitFactory;
    }

    @Reference
    protected void setCommitFactory(CommitFactory commitFactory) {
        this.commitFactory = commitFactory;
    }

    @Reference
    protected void setRevisionFactory(RevisionFactory revisionFactory) {
        this.revisionFactory = revisionFactory;
    }

    @Reference
    protected void setVersionedRDFRecordFactory(VersionedRDFRecordFactory versionedRDFRecordFactory) {
        this.versionedRDFRecordFactory = versionedRDFRecordFactory;
    }

    @Reference
    protected void setVersionedRecordFactory(VersionedRecordFactory versionedRecordFactory) {
        this.versionedRecordFactory = versionedRecordFactory;
    }

    @Reference
    protected void setUnversionedRecordFactory(UnversionedRecordFactory unversionedRecordFactory) {
        this.unversionedRecordFactory = unversionedRecordFactory;
    }

    @Reference
    protected void setVersionFactory(VersionFactory versionFactory) {
        this.versionFactory = versionFactory;
    }

    private static final String PROV_AT_TIME = "http://www.w3.org/ns/prov#atTime";

    private static final String RECORD_NAMESPACE = "https://matonto.org/records#";
    private static final String DISTRIBUTION_NAMESPACE = "https://matonto.org/distributions#";
    private static final String VERSION_NAMESPACE = "https://matonto.org/versions#";
    private static final String BRANCH_NAMESPACE = "https://matonto.org/branches#";
    private static final String IN_PROGRESS_COMMIT_NAMESPACE = "https://matonto.org/in-progress-commits#";
    private static final String COMMIT_NAMESPACE = "https://matonto.org/commits#";
    private static final String REVISION_NAMESPACE = "https://matonto.org/revisions#";
    private static final String ADDITIONS_NAMESPACE = "https://matonto.org/additions#";
    private static final String DELETIONS_NAMESPACE = "https://matonto.org/deletions#";
    private static final String DELETION_CONTEXT = "https://matonto.org/is-a-deletion#";

    private static final String FIND_RECORDS_QUERY;
    private static final String COUNT_RECORDS_QUERY;
    private static final String GET_NEW_LATEST_VERSION;
    private static final String GET_COMMIT_CHAIN;
    private static final String GET_IN_PROGRESS_COMMIT;
    private static final String COMMIT_BINDING = "commit";
    private static final String PARENT_BINDING = "parent";
    private static final String RECORD_BINDING = "record";
    private static final String CATALOG_BINDING = "catalog";
    private static final String RECORD_COUNT_BINDING = "record_count";
    private static final String TYPE_FILTER_BINDING = "type_filter";
    private static final String SEARCH_BINDING = "search_text";
    private static final String USER_BINDING = "user";

    static {
        try {
            FIND_RECORDS_QUERY = IOUtils
                    .toString(SimpleCatalogManager.class.getResourceAsStream("/find-records.rq"), "UTF-8");
            COUNT_RECORDS_QUERY = IOUtils
                    .toString(SimpleCatalogManager.class.getResourceAsStream("/count-records.rq"), "UTF-8");
            GET_NEW_LATEST_VERSION = IOUtils.toString(
                    SimpleCatalogManager.class.getResourceAsStream("/get-new-latest-version.rq"), "UTF-8");
            GET_COMMIT_CHAIN = IOUtils
                    .toString(SimpleCatalogManager.class.getResourceAsStream("/get-commit-chain.rq"), "UTF-8");
            GET_IN_PROGRESS_COMMIT = IOUtils.toString(
                    SimpleCatalogManager.class.getResourceAsStream("/get-in-progress-commit.rq"), "UTF-8");
        } catch (IOException e) {
            throw new MatOntoException(e);
        }
    }

    @Activate
    protected void start(Map<String, Object> props) {
        CatalogConfig config = Configurable.createConfigurable(CatalogConfig.class, props);
        distributedCatalogIRI = vf.createIRI(config.iri() + "-distributed");
        localCatalogIRI = vf.createIRI(config.iri() + "-local");
        createSortingOptions();

        if (!resourceExists(distributedCatalogIRI, Catalog.TYPE)) {
            log.debug("Initializing the distributed MatOnto Catalog.");
            addCatalogToRepo(distributedCatalogIRI, config.title() + " (Distributed)", config.description());
        }

        if (!resourceExists(localCatalogIRI, Catalog.TYPE)) {
            log.debug("Initializing the local MatOnto Catalog.");
            addCatalogToRepo(localCatalogIRI, config.title() + " (Local)", config.description());
        }
    }

    @Modified
    protected void modified(Map<String, Object> props) {
        start(props);
    }

    @Override
    public Catalog getDistributedCatalog() throws MatOntoException {
        return getCatalog(distributedCatalogIRI);
    }

    @Override
    public Catalog getLocalCatalog() throws MatOntoException {
        return getCatalog(localCatalogIRI);
    }

    @Override
    public PaginatedSearchResults<Record> findRecord(Resource catalogId, PaginatedSearchParams searchParams)
            throws MatOntoException {
        try (RepositoryConnection conn = repository.getConnection()) {
            Optional<Resource> typeParam = searchParams.getTypeFilter();
            Optional<String> searchTextParam = searchParams.getSearchText();

            // Get Total Count
            TupleQuery countQuery = conn.prepareTupleQuery(COUNT_RECORDS_QUERY);
            countQuery.setBinding(CATALOG_BINDING, catalogId);
            typeParam.ifPresent(resource -> countQuery.setBinding(TYPE_FILTER_BINDING, resource));
            searchTextParam.ifPresent(s -> countQuery.setBinding(SEARCH_BINDING, vf.createLiteral(s)));

            TupleQueryResult countResults = countQuery.evaluate();

            int totalCount;
            BindingSet countBindingSet;
            if (countResults.hasNext()
                    && (countBindingSet = countResults.next()).getBindingNames().contains(RECORD_COUNT_BINDING)) {
                totalCount = Bindings.requiredLiteral(countBindingSet, RECORD_COUNT_BINDING).intValue();
                countResults.close();
            } else {
                countResults.close();
                conn.close();
                return SearchResults.emptyResults();
            }

            log.debug("Record count: " + totalCount);

            // Prepare Query
            int limit = searchParams.getLimit();
            int offset = searchParams.getOffset();

            if (offset > totalCount) {
                throw new MatOntoException("Offset exceeds total size");
            }

            String sortBinding;
            Resource sortByParam = searchParams.getSortBy();
            if (sortingOptions.get(sortByParam) != null) {
                sortBinding = sortingOptions.get(sortByParam);
            } else {
                log.warn("sortBy parameter must be in the allowed list. Sorting by modified date instead.");
                sortBinding = "modified";
            }

            String querySuffix;
            Optional<Boolean> ascendingParam = searchParams.getAscending();
            if (ascendingParam.isPresent() && ascendingParam.get()) {
                querySuffix = String.format("\nORDER BY ?%s\nLIMIT %d\nOFFSET %d", sortBinding, limit, offset);
            } else {
                querySuffix = String.format("\nORDER BY DESC(?%s)\nLIMIT %d\nOFFSET %d", sortBinding, limit,
                        offset);
            }

            String queryString = FIND_RECORDS_QUERY + querySuffix;
            TupleQuery query = conn.prepareTupleQuery(queryString);
            query.setBinding(CATALOG_BINDING, catalogId);
            typeParam.ifPresent(resource -> query.setBinding(TYPE_FILTER_BINDING, resource));
            searchTextParam.ifPresent(searchText -> query.setBinding(SEARCH_BINDING, vf.createLiteral(searchText)));

            log.debug("Query String:\n" + queryString);
            log.debug("Query Plan:\n" + query);

            // Get Results
            TupleQueryResult result = query.evaluate();

            List<Record> records = new ArrayList<>();
            BindingSet resultsBindingSet;
            while (result.hasNext()
                    && (resultsBindingSet = result.next()).getBindingNames().contains(RECORD_BINDING)) {
                Resource resource = vf
                        .createIRI(Bindings.requiredResource(resultsBindingSet, RECORD_BINDING).stringValue());
                Record record = processRecordBindingSet(resultsBindingSet, resource);
                records.add(record);
            }

            result.close();
            conn.close();

            log.debug("Result set size: " + records.size());

            int pageNumber = (offset / limit) + 1;

            if (records.size() > 0) {
                return new SimpleSearchResults<>(records, totalCount, limit, pageNumber);
            } else {
                return SearchResults.emptyResults();
            }
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection.", e);
        }
    }

    @Override
    public Set<Resource> getRecordIds(Resource catalogId) throws MatOntoException {
        if (resourceExists(catalogId, Catalog.TYPE)) {
            try (RepositoryConnection conn = repository.getConnection()) {
                Set<Resource> results = new HashSet<>();
                RepositoryResult<Statement> statements = conn.getStatements(null, vf.createIRI(Record.catalog_IRI),
                        catalogId);
                statements.forEach(statement -> results.add(statement.getSubject()));
                return results;
            } catch (RepositoryException e) {
                throw new MatOntoException("Error in repository connection.", e);
            }
        }
        return Collections.emptySet();
    }

    @Override
    public <T extends Record> T createRecord(RecordConfig config, OrmFactory<T> factory) {
        OffsetDateTime now = OffsetDateTime.now();
        return addPropertiesToRecord(factory.createNew(vf.createIRI(RECORD_NAMESPACE + UUID.randomUUID())), config,
                now, now);
    }

    @Override
    public <T extends Record> void addRecord(Resource catalogId, T record) throws MatOntoException {
        try (RepositoryConnection conn = repository.getConnection()) {
            IRI identifierIRI = vf.createIRI(DCTERMS.IDENTIFIER.stringValue());
            Value identifier = record.getProperty(identifierIRI)
                    .orElseThrow(() -> new MatOntoException("The Record must have an identifier."));
            if (resourceExists(catalogId, Catalog.TYPE) && !resourceExists(record.getResource())
                    && !conn.getStatements(null, identifierIRI, identifier).hasNext()) {
                record.setCatalog(getCatalog(catalogId));
                conn.add(record.getModel(), record.getResource());
            } else {
                throw new MatOntoException("The Record could not be added.");
            }
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection", e);
        }
    }

    @Override
    public <T extends Record> void updateRecord(Resource catalogId, T newRecord) throws MatOntoException {
        if (resourceExists(catalogId, Catalog.TYPE) && resourceExists(newRecord.getResource(), T.TYPE)) {
            try (RepositoryConnection conn = repository.getConnection()) {
                if (conn.getStatements(newRecord.getResource(), vf.createIRI(Record.catalog_IRI), catalogId)
                        .hasNext()) {
                    update(newRecord.getResource(), newRecord.getModel());
                }
            } catch (RepositoryException e) {
                throw new MatOntoException("Error in repository connection", e);
            }
        } else {
            throw new MatOntoException("The Record could not be updated.");
        }
    }

    @Override
    public void removeRecord(Resource catalogId, Resource recordId) throws MatOntoException {
        Optional<Record> optionalRecord = getRecord(catalogId, recordId, recordFactory);
        if (optionalRecord.isPresent()) {
            Record record = optionalRecord.get();
            if (record.getModel().contains(null, null, vf.createIRI(UnversionedRecord.TYPE))) {
                removeUnversionedRecord(record);
            } else if (record.getModel().contains(null, null, vf.createIRI(VersionedRDFRecord.TYPE))) {
                removeVersionedRDFRecord(record);
            } else if (record.getModel().contains(null, null, vf.createIRI(VersionedRecord.TYPE))) {
                removeVersionedRecord(record);
            } else {
                remove(recordId);
            }
        } else {
            throw new MatOntoException("The Record could not be removed.");
        }
    }

    @Override
    public <T extends Record> Optional<T> getRecord(Resource catalogId, Resource recordId, OrmFactory<T> factory)
            throws MatOntoException {
        try (RepositoryConnection conn = repository.getConnection()) {
            boolean condition = resourceExists(recordId, factory.getTypeIRI().stringValue())
                    && conn.getStatements(recordId, vf.createIRI(Record.catalog_IRI), catalogId).hasNext();
            return getObject(condition, recordId, factory);
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection.", e);
        }
    }

    @Override
    public <T extends Record> Optional<T> getRecord(String identifier, OrmFactory<T> factory)
            throws MatOntoException {
        try (RepositoryConnection conn = repository.getConnection()) {
            RepositoryResult<Statement> statements = conn.getStatements(null,
                    vf.createIRI(DCTERMS.IDENTIFIER.stringValue()), vf.createLiteral(identifier));
            if (statements.hasNext()) {
                return getObject(true, statements.next().getSubject(), factory);
            }
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection.", e);
        }
        return Optional.empty();
    }

    @Override
    public Distribution createDistribution(DistributionConfig config) {
        OffsetDateTime now = OffsetDateTime.now();

        Distribution distribution = distributionFactory
                .createNew(vf.createIRI(DISTRIBUTION_NAMESPACE + UUID.randomUUID()));
        distribution.setProperty(vf.createLiteral(config.getTitle()), vf.createIRI(DCTERMS.TITLE.stringValue()));
        distribution.setProperty(vf.createLiteral(now), vf.createIRI(DCTERMS.ISSUED.stringValue()));
        distribution.setProperty(vf.createLiteral(now), vf.createIRI(DCTERMS.MODIFIED.stringValue()));
        if (config.getDescription() != null) {
            distribution.setProperty(vf.createLiteral(config.getDescription()),
                    vf.createIRI(DCTERMS.DESCRIPTION.stringValue()));
        }
        if (config.getFormat() != null) {
            distribution.setProperty(vf.createLiteral(config.getFormat()),
                    vf.createIRI(DCTERMS.FORMAT.stringValue()));
        }
        if (config.getAccessURL() != null) {
            distribution.setAccessURL(config.getAccessURL());
        }
        if (config.getDownloadURL() != null) {
            distribution.setDownloadURL(config.getDownloadURL());
        }

        return distribution;
    }

    @Override
    public void addDistributionToUnversionedRecord(Distribution distribution, Resource unversionedRecordId)
            throws MatOntoException {
        if (!addDistribution(distribution, unversionedRecordId, UnversionedRecord.unversionedDistribution_IRI)) {
            throw new MatOntoException("The Distribution could not be added.");
        }
    }

    @Override
    public void addDistributionToVersion(Distribution distribution, Resource versionId) throws MatOntoException {
        if (!addDistribution(distribution, versionId, Version.versionedDistribution_IRI)) {
            throw new MatOntoException("The Distribution could not be added.");
        }
    }

    @Override
    public void updateDistribution(Distribution newDistribution) throws MatOntoException {
        if (resourceExists(newDistribution.getResource(), Distribution.TYPE)) {
            update(newDistribution.getResource(), newDistribution.getModel());
        } else {
            throw new MatOntoException("The Distribution could not be updated.");
        }
    }

    @Override
    public void removeDistributionFromUnversionedRecord(Resource distributionId, Resource unversionedRecordId)
            throws MatOntoException {
        if (!(resourceExists(unversionedRecordId, UnversionedRecord.TYPE)
                && resourceExists(distributionId, Distribution.TYPE) && removeObjectWithRelationship(distributionId,
                        unversionedRecordId, UnversionedRecord.unversionedDistribution_IRI))) {
            throw new MatOntoException("The Distribution could not be removed.");
        }
    }

    @Override
    public void removeDistributionFromVersion(Resource distributionId, Resource versionId) throws MatOntoException {
        if (!(resourceExists(versionId, Version.TYPE) && resourceExists(distributionId, Distribution.TYPE)
                && removeObjectWithRelationship(distributionId, versionId, Version.versionedDistribution_IRI))) {
            throw new MatOntoException("The Distribution could not be removed.");
        }
    }

    @Override
    public Optional<Distribution> getDistribution(Resource distributionId) throws MatOntoException {
        return getObject(resourceExists(distributionId, Distribution.TYPE), distributionId, distributionFactory);
    }

    @Override
    public <T extends Version> T createVersion(@Nonnull String title, String description, OrmFactory<T> factory) {
        OffsetDateTime now = OffsetDateTime.now();

        T version = factory.createNew(vf.createIRI(VERSION_NAMESPACE + UUID.randomUUID()));
        version.setProperty(vf.createLiteral(title), vf.createIRI(DCTERMS.TITLE.stringValue()));
        if (description != null) {
            version.setProperty(vf.createLiteral(description), vf.createIRI(DCTERMS.DESCRIPTION.stringValue()));
        }
        version.setProperty(vf.createLiteral(now), vf.createIRI(DCTERMS.ISSUED.stringValue()));
        version.setProperty(vf.createLiteral(now), vf.createIRI(DCTERMS.MODIFIED.stringValue()));

        return version;
    }

    @Override
    public <T extends Version> void addVersion(T version, Resource versionedRecordId) throws MatOntoException {
        if (!resourceExists(version.getResource()) && resourceExists(versionedRecordId, VersionedRecord.TYPE)) {
            try (RepositoryConnection conn = repository.getConnection()) {
                IRI latestVersionResource = vf.createIRI(VersionedRecord.latestVersion_IRI);
                IRI versionResource = vf.createIRI(VersionedRecord.version_IRI);
                conn.begin();
                conn.remove(versionedRecordId, latestVersionResource, null, versionedRecordId);
                conn.add(versionedRecordId, latestVersionResource, version.getResource(), versionedRecordId);
                conn.add(versionedRecordId, versionResource, version.getResource(), versionedRecordId);
                conn.add(version.getModel(), version.getResource());
                conn.commit();
            } catch (RepositoryException e) {
                throw new MatOntoException("Error in repository connection", e);
            }
        } else {
            throw new MatOntoException("Version could not be added.");
        }
    }

    @Override
    public <T extends Version> void updateVersion(T newVersion) throws MatOntoException {
        if (resourceExists(newVersion.getResource(), T.TYPE)) {
            update(newVersion.getResource(), newVersion.getModel());
        } else {
            throw new MatOntoException("The Version could not be updated.");
        }
    }

    @Override
    public void removeVersion(Resource versionId, Resource versionedRecordId) throws MatOntoException {
        Optional<Version> optionalVersion = getVersion(versionId, versionFactory);
        if (optionalVersion.isPresent() && resourceExists(versionedRecordId, VersionedRecord.TYPE)
                && removeObjectWithRelationship(versionId, versionedRecordId, VersionedRecord.version_IRI)) {
            try (RepositoryConnection conn = repository.getConnection()) {
                IRI latestVersionIRI = vf.createIRI(VersionedRecord.latestVersion_IRI);
                if (conn.getStatements(versionedRecordId, latestVersionIRI, versionId, versionedRecordId)
                        .hasNext()) {
                    conn.begin();
                    conn.remove(versionedRecordId, latestVersionIRI, versionId, versionedRecordId);
                    TupleQuery query = conn.prepareTupleQuery(GET_NEW_LATEST_VERSION);
                    query.setBinding(RECORD_BINDING, versionedRecordId);
                    TupleQueryResult result = query.evaluate();

                    Optional<Binding> binding;
                    if (result.hasNext() && (binding = result.next().getBinding("version")).isPresent()) {
                        conn.add(versionedRecordId, latestVersionIRI, binding.get().getValue(), versionedRecordId);
                    }
                    conn.commit();
                }
                Version version = optionalVersion.get();
                version.getVersionedDistribution().forEach(distribution -> remove(distribution.getResource()));
            } catch (RepositoryException e) {
                throw new MatOntoException("Error in repository connection", e);
            }
        } else {
            throw new MatOntoException("The Version could not be removed.");
        }
    }

    @Override
    public <T extends Version> Optional<T> getVersion(Resource versionId, OrmFactory<T> factory)
            throws MatOntoException {
        return getObject(resourceExists(versionId, factory.getTypeIRI().stringValue()), versionId, factory);
    }

    @Override
    public <T extends Branch> T createBranch(@Nonnull String title, String description, OrmFactory<T> factory) {
        OffsetDateTime now = OffsetDateTime.now();

        T branch = factory.createNew(vf.createIRI(BRANCH_NAMESPACE + UUID.randomUUID()));
        branch.setProperty(vf.createLiteral(title), vf.createIRI(DCTERMS.TITLE.stringValue()));
        branch.setProperty(vf.createLiteral(now), vf.createIRI(DCTERMS.ISSUED.stringValue()));
        branch.setProperty(vf.createLiteral(now), vf.createIRI(DCTERMS.MODIFIED.stringValue()));
        if (description != null) {
            branch.setProperty(vf.createLiteral(description), vf.createIRI(DCTERMS.DESCRIPTION.stringValue()));
        }

        return branch;
    }

    @Override
    public <T extends Branch> void addBranch(T branch, Resource versionedRDFRecordId) throws MatOntoException {
        if (!resourceExists(branch.getResource())
                && resourceExists(versionedRDFRecordId, VersionedRDFRecord.TYPE)) {
            try (RepositoryConnection conn = repository.getConnection()) {
                conn.begin();
                conn.add(versionedRDFRecordId, vf.createIRI(VersionedRDFRecord.branch_IRI), branch.getResource(),
                        versionedRDFRecordId);
                conn.add(branch.getModel(), branch.getResource());
                conn.commit();
            } catch (RepositoryException e) {
                throw new MatOntoException("Error in repository connection", e);
            }
        } else {
            throw new MatOntoException("The Branch could not be added.");
        }
    }

    @Override
    public void addMasterBranch(Resource versionedRDFRecordId) throws MatOntoException {
        try (RepositoryConnection conn = repository.getConnection()) {
            IRI masterBranchIRI = vf.createIRI(VersionedRDFRecord.masterBranch_IRI);
            if (conn.getStatements(versionedRDFRecordId, masterBranchIRI, null, versionedRDFRecordId).hasNext()) {
                throw new MatOntoException("The Record already has a master Branch.");
            } else if (resourceExists(versionedRDFRecordId, VersionedRDFRecord.TYPE)) {
                Branch branch = createBranch("MASTER", "The master branch.", branchFactory);
                conn.begin();
                conn.add(versionedRDFRecordId, vf.createIRI(VersionedRDFRecord.branch_IRI), branch.getResource(),
                        versionedRDFRecordId);
                conn.add(versionedRDFRecordId, masterBranchIRI, branch.getResource(), versionedRDFRecordId);
                conn.add(branch.getModel(), branch.getResource());
                conn.commit();
            } else {
                throw new MatOntoException("The master Branch could not be added.");
            }
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection", e);
        }
    }

    @Override
    public <T extends Branch> void updateBranch(T newBranch) throws MatOntoException {
        try (RepositoryConnection conn = repository.getConnection()) {
            IRI masterBranchIRI = vf.createIRI(VersionedRDFRecord.masterBranch_IRI);

            if (!resourceExists(newBranch.getResource(), T.TYPE)) {
                throw new MatOntoException("The Branch could not be updated. Branch does not exist.");
            }
            if (conn.getStatements(null, masterBranchIRI, newBranch.getResource()).hasNext()) {
                throw new MatOntoException("The Branch could not be updated. Master Branch cannot be updated.");
            }

            update(newBranch.getResource(), newBranch.getModel());
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection", e);
        }
    }

    @Override
    public void updateHead(Resource branch, Resource commit) throws MatOntoException {
        try (RepositoryConnection conn = repository.getConnection()) {
            conn.begin();
            updateHead(branch, commit, conn);
            conn.commit();
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection", e);
        }
    }

    /**
     * Updates the head of a branch to point to the specified commit. Connection transaction control and lifecycle
     * (i.e. calling close()) should be performed outside of this method.
     *
     * @param branch The branch whose head to update.
     * @param commit The new head commit of the specified branch.
     * @param conn The RepositoryConnection to use.
     * @throws MatOntoException If the Branch or Commit do not exist.
     * @throws RepositoryException If there is a problem communicating with the Repository.
     */
    private void updateHead(Resource branch, Resource commit, RepositoryConnection conn) throws MatOntoException {
        if (!resourceExists(branch, Branch.TYPE, conn)) {
            throw new MatOntoException("The Commit could not be added. The branch does not exist");
        }
        if (!resourceExists(commit, Commit.TYPE, conn)) {
            throw new MatOntoException("The Commit could not be added. The commit does not exist");
        }

        IRI headIRI = vf.createIRI(Branch.head_IRI);
        conn.remove(branch, headIRI, null, branch);
        conn.add(branch, headIRI, commit, branch);
    }

    @Override
    public void removeBranch(Resource branchId, Resource versionedRDFRecordId) throws MatOntoException {
        Optional<Branch> optionalBranch = getBranch(branchId, branchFactory);
        try (RepositoryConnection conn = repository.getConnection()) {
            IRI masterBranchIRI = vf.createIRI(VersionedRDFRecord.masterBranch_IRI);
            if (conn.getStatements(versionedRDFRecordId, masterBranchIRI, branchId, versionedRDFRecordId)
                    .hasNext()) {
                throw new MatOntoException("The master Branch cannot be removed.");
            } else if (optionalBranch.isPresent() && resourceExists(versionedRDFRecordId, VersionedRDFRecord.TYPE)
                    && removeObjectWithRelationship(branchId, versionedRDFRecordId,
                            VersionedRDFRecord.branch_IRI)) {
                Branch branch = optionalBranch.get();
                Optional<Commit> headCommit = branch.getHead();
                if (headCommit.isPresent()) {
                    List<Resource> chain = getCommitChain(headCommit.get().getResource());
                    IRI headCommitIRI = vf.createIRI(Branch.head_IRI);
                    IRI wasInformedByIRI = vf.createIRI(Activity.wasInformedBy_IRI);
                    IRI commitIRI = vf.createIRI(Tag.commit_IRI);
                    conn.begin();
                    for (int i = chain.size() - 1; i >= 0; i--) {
                        Resource commitId = chain.get(i);
                        if (!conn.getStatements(null, headCommitIRI, commitId).hasNext()
                                && !conn.getStatements(null, wasInformedByIRI, commitId).hasNext()) {
                            conn.remove((Resource) null, null, null, commitId);
                            conn.remove((Resource) null, commitIRI, commitId);
                        } else {
                            break;
                        }
                    }
                    conn.commit();
                } else {
                    log.warn("The HEAD Commit was not set on the Branch.");
                }
            } else {
                throw new MatOntoException("The Branch could not be removed.");
            }
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection", e);
        }
    }

    @Override
    public <T extends Branch> Optional<T> getBranch(Resource branchId, OrmFactory<T> factory)
            throws MatOntoException {
        return getObject(resourceExists(branchId, factory.getTypeIRI().stringValue()), branchId, factory);
    }

    @Override
    public Commit createCommit(@Nonnull InProgressCommit inProgressCommit, Set<Commit> parents,
            @Nonnull String message) {
        IRI associatedWith = vf.createIRI(Activity.wasAssociatedWith_IRI);
        IRI informedBy = vf.createIRI(Activity.wasInformedBy_IRI);
        OffsetDateTime now = OffsetDateTime.now();
        Value user = inProgressCommit.getProperty(associatedWith).get();
        String metadata = now.toString() + user.stringValue();
        IRI generatedIRI = vf.createIRI(Activity.generated_IRI);

        if (parents != null) {
            metadata += parents.stream()
                    .sorted(Comparator.comparing(commit2 -> commit2.getResource().stringValue()))
                    .map(commit -> commit.getResource().stringValue()).collect(Collectors.joining(""));
        }

        Commit commit = commitFactory.createNew(vf.createIRI(COMMIT_NAMESPACE + DigestUtils.shaHex(metadata)));
        commit.setProperty(inProgressCommit.getProperty(generatedIRI).get(), generatedIRI);
        commit.setProperty(vf.createLiteral(now), vf.createIRI(PROV_AT_TIME));
        commit.setProperty(vf.createLiteral(message), vf.createIRI(DCTERMS.TITLE.stringValue()));
        commit.setProperty(user, associatedWith);

        Model revisionModel = mf.createModel(inProgressCommit.getModel());
        revisionModel.remove(inProgressCommit.getResource(), null, null);
        Revision revision = revisionFactory.getExisting((Resource) inProgressCommit.getProperty(generatedIRI).get(),
                revisionModel);

        if (parents != null) {
            revision.setProperties(parents.stream().map(parent -> parent.getProperty(generatedIRI).get())
                    .collect(Collectors.toSet()), vf.createIRI(Entity.wasDerivedFrom_IRI));
            commit.setProperties(parents.stream().map(Commit::getResource).collect(Collectors.toSet()), informedBy);
        }

        commit.getModel().addAll(revisionModel);
        return commit;
    }

    @Override
    public InProgressCommit createInProgressCommit(User user, Resource recordId) throws InvalidParameterException {
        if (!resourceExists(recordId, VersionedRDFRecord.TYPE)) {
            throw new InvalidParameterException("The provided Resource does not identify a Record entity.");
        } else if (getInProgressCommitIRI(user.getResource(), recordId).isPresent()) {
            throw new MatOntoException("The user already has an InProgressCommit for the identified Record.");
        } else {
            UUID uuid = UUID.randomUUID();

            Revision revision = revisionFactory.createNew(vf.createIRI(REVISION_NAMESPACE + uuid));
            revision.setAdditions(vf.createIRI(ADDITIONS_NAMESPACE + uuid));
            revision.setDeletions(vf.createIRI(DELETIONS_NAMESPACE + uuid));

            InProgressCommit inProgressCommit = inProgressCommitFactory
                    .createNew(vf.createIRI(IN_PROGRESS_COMMIT_NAMESPACE + uuid));
            inProgressCommit.setOnVersionedRDFRecord(versionedRDFRecordFactory.createNew(recordId));
            inProgressCommit.setProperty(user.getResource(), vf.createIRI(Activity.wasAssociatedWith_IRI));
            inProgressCommit.setProperty(revision.getResource(), vf.createIRI(Activity.generated_IRI));
            inProgressCommit.getModel().addAll(revision.getModel());

            return inProgressCommit;
        }
    }

    @Override
    public void addAdditions(Model statements, Resource commitId) throws MatOntoException {
        if (resourceExists(commitId, InProgressCommit.TYPE)) {
            try (RepositoryConnection conn = repository.getConnection()) {
                Resource additions = getAdditionsResource(commitId, conn);
                Resource deletions = getDeletionsResource(commitId, conn);
                conn.begin();
                for (Statement statement : statements) {
                    if (!conn.getStatements(statement.getSubject(), statement.getPredicate(), statement.getObject(),
                            deletions).hasNext()) {
                        conn.add(statement, additions);
                    } else {
                        conn.remove(statement, deletions);
                    }
                }
                conn.commit();
            } catch (RepositoryException e) {
                throw new MatOntoException("Error in repository connection", e);
            }
        } else {
            throw new MatOntoException("The additions could not be added.");
        }
    }

    @Override
    public void addDeletions(Model statements, Resource commitId) throws MatOntoException {
        if (resourceExists(commitId, InProgressCommit.TYPE)) {
            try (RepositoryConnection conn = repository.getConnection()) {
                Resource additions = getAdditionsResource(commitId, conn);
                Resource deletions = getDeletionsResource(commitId, conn);
                conn.begin();
                for (Statement statement : statements) {
                    if (!conn.getStatements(statement.getSubject(), statement.getPredicate(), statement.getObject(),
                            additions).hasNext()) {
                        conn.add(statement, deletions);
                    } else {
                        conn.remove(statement, additions);
                    }
                }
                conn.commit();
            } catch (RepositoryException e) {
                throw new MatOntoException("Error in repository connection", e);
            }
        } else {
            throw new MatOntoException("The deletions could not be added.");
        }
    }

    @Override
    public void addCommitToBranch(Commit commit, Resource branchId) throws MatOntoException {
        try (RepositoryConnection conn = repository.getConnection()) {
            if (!resourceExists(branchId, Branch.TYPE, conn)) {
                throw new MatOntoException("The Commit could not be added. Branch does not exist.");
            }
            if (resourceExists(commit.getResource(), conn)) {
                throw new MatOntoException("The Commit could not be added. The commit already exists.");
            }

            conn.begin();
            conn.add(commit.getModel(), commit.getResource());
            updateHead(branchId, commit.getResource(), conn);
            conn.commit();
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection", e);
        }
    }

    @Override
    public void addInProgressCommit(InProgressCommit inProgressCommit) throws MatOntoException {
        if (!resourceExists(inProgressCommit.getResource())) {
            try (RepositoryConnection conn = repository.getConnection()) {
                conn.add(inProgressCommit.getModel(), inProgressCommit.getResource());
            } catch (RepositoryException e) {
                throw new MatOntoException("Error in repository connection", e);
            }
        } else {
            throw new MatOntoException("The InProgressCommit could not be added.");
        }
    }

    @Override
    public <T extends Commit> Optional<T> getCommit(Resource commitId, OrmFactory<T> factory)
            throws MatOntoException {
        return getObject(resourceExists(commitId, factory.getTypeIRI().stringValue()), commitId, factory);
    }

    @Override
    public Optional<Resource> getInProgressCommitIRI(Resource userId, Resource recordId) throws MatOntoException {
        try (RepositoryConnection conn = repository.getConnection()) {
            TupleQuery query = conn.prepareTupleQuery(GET_IN_PROGRESS_COMMIT);
            query.setBinding(USER_BINDING, userId);
            query.setBinding(RECORD_BINDING, recordId);
            TupleQueryResult queryResult = query.evaluate();
            if (queryResult.hasNext()) {
                return Optional.of(Bindings.requiredResource(queryResult.next(), COMMIT_BINDING));
            } else {
                return Optional.empty();
            }
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection.", e);
        }
    }

    @Override
    public Difference getCommitDifference(Resource commitId) throws MatOntoException {
        return getCommitDifference(commitId, commitFactory);
    }

    private <T extends Commit> Difference getCommitDifference(Resource commitId, OrmFactory<T> commitFactory) {
        T commit = getCommit(commitId, commitFactory)
                .orElseThrow(() -> new MatOntoException("The Commit could not be retrieved."));
        try (RepositoryConnection conn = repository.getConnection()) {
            Resource revisionIRI = (Resource) commit.getProperty(vf.createIRI(Activity.generated_IRI)).get();
            Revision revision = revisionFactory.getExisting(revisionIRI, commit.getModel());
            Resource additionsIRI = (Resource) revision.getAdditions()
                    .orElseThrow(() -> new MatOntoException("The additions could not be found."));
            Resource deletionsIRI = (Resource) revision.getDeletions()
                    .orElseThrow(() -> new MatOntoException("The deletions could not be found."));
            Model addModel = mf.createModel();
            Model deleteModel = mf.createModel();
            conn.getStatements(null, null, null, additionsIRI).forEach(statement -> addModel
                    .add(statement.getSubject(), statement.getPredicate(), statement.getObject()));
            conn.getStatements(null, null, null, deletionsIRI).forEach(statement -> deleteModel
                    .add(statement.getSubject(), statement.getPredicate(), statement.getObject()));
            return new SimpleDifference.Builder().additions(addModel).deletions(deleteModel).build();
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection", e);
        }
    }

    @Override
    public void removeInProgressCommit(Resource inProgressCommitId) throws MatOntoException {
        if (resourceExists(inProgressCommitId, InProgressCommit.TYPE)) {
            remove(inProgressCommitId);
        } else {
            throw new MatOntoException("The InProgressCommit could not be removed.");
        }
    }

    @Override
    public Model applyInProgressCommit(Resource inProgressCommitId, Model entity) throws MatOntoException {
        Difference diff = getCommitDifference(inProgressCommitId, inProgressCommitFactory);
        Model result = mf.createModel(entity);
        diff.getAdditions().forEach(result::add);
        diff.getDeletions().forEach(statement -> result.remove(statement.getSubject(), statement.getPredicate(),
                statement.getObject()));
        return result;
    }

    @Override
    public List<Resource> getCommitChain(Resource commitId) throws MatOntoException {
        List<Resource> results = new ArrayList<>();
        if (resourceExists(commitId, Commit.TYPE)) {
            try (RepositoryConnection conn = repository.getConnection()) {
                Iterator<Value> commits = getCommitChainIterator(commitId, conn);
                commits.forEachRemaining(commit -> results.add((Resource) commit));
                return results;
            } catch (RepositoryException e) {
                throw new MatOntoException("Error in repository connection", e);
            }
        }
        return results;
    }

    @Override
    public Optional<Model> getCompiledResource(Resource commitId) throws MatOntoException {
        if (resourceExists(commitId, Commit.TYPE)) {
            try (RepositoryConnection conn = repository.getConnection()) {
                Iterator<Value> iterator = getCommitChainIterator(commitId, conn);
                Model model = createModelFromIterator(iterator, conn);
                model.remove(null, null, null, vf.createIRI(DELETION_CONTEXT));
                return Optional.of(model);
            } catch (RepositoryException e) {
                throw new MatOntoException("Error in repository connection", e);
            }
        }
        return Optional.empty();
    }

    @Override
    public Set<Conflict> getConflicts(Resource leftId, Resource rightId) throws MatOntoException {
        if (resourceExists(leftId, Commit.TYPE) && resourceExists(rightId, Commit.TYPE)) {
            try (RepositoryConnection conn = repository.getConnection()) {
                LinkedList<Value> leftList = new LinkedList<>();
                LinkedList<Value> rightList = new LinkedList<>();

                getCommitChainIterator(leftId, conn).forEachRemaining(leftList::add);
                getCommitChainIterator(rightId, conn).forEachRemaining(rightList::add);

                ListIterator<Value> leftIterator = leftList.listIterator();
                ListIterator<Value> rightIterator = rightList.listIterator();

                Value originalEnd = null;
                while (leftIterator.hasNext() && rightIterator.hasNext()) {
                    Value currentId = leftIterator.next();
                    if (!currentId.equals(rightIterator.next())) {
                        leftIterator.previous();
                        rightIterator.previous();
                        break;
                    } else {
                        originalEnd = currentId;
                    }
                }
                if (originalEnd == null) {
                    throw new MatOntoException("There is no common parent between the provided Commits.");
                }

                Model left = createModelFromIterator(leftIterator, conn);
                Model right = createModelFromIterator(rightIterator, conn);

                Model duplicates = mf.createModel(left);
                duplicates.retainAll(right);

                left.removeAll(duplicates);
                right.removeAll(duplicates);

                Resource deletionContext = vf.createIRI(DELETION_CONTEXT);

                Model leftDeletions = mf.createModel(left.filter(null, null, null, deletionContext));
                Model rightDeletions = mf.createModel(right.filter(null, null, null, deletionContext));

                left.removeAll(leftDeletions);
                right.removeAll(rightDeletions);

                Set<Conflict> result = new HashSet<>();

                Model original = getCompiledResource((Resource) originalEnd).get();
                IRI rdfType = vf.createIRI(RDF.TYPE.stringValue());

                leftDeletions.forEach(statement -> {
                    Resource subject = statement.getSubject();
                    IRI predicate = statement.getPredicate();
                    if (predicate.equals(rdfType) || right.contains(subject, predicate, null)) {
                        result.add(createConflict(subject, predicate, original, left, leftDeletions, right,
                                rightDeletions));
                        Stream.of(left, right, rightDeletions)
                                .forEach(item -> item.remove(subject, predicate, null));
                    }
                });

                rightDeletions.forEach(statement -> {
                    Resource subject = statement.getSubject();
                    IRI predicate = statement.getPredicate();
                    if (predicate.equals(rdfType) || left.contains(subject, predicate, null)) {
                        result.add(createConflict(subject, predicate, original, left, leftDeletions, right,
                                rightDeletions));
                        Stream.of(left, leftDeletions, right)
                                .forEach(item -> item.remove(subject, predicate, null));
                    }
                });

                left.forEach(statement -> {
                    Resource subject = statement.getSubject();
                    IRI predicate = statement.getPredicate();
                    if (right.contains(subject, predicate, null)) {
                        result.add(createConflict(subject, predicate, original, left, leftDeletions, right,
                                rightDeletions));
                        Stream.of(leftDeletions, right, rightDeletions)
                                .forEach(item -> item.remove(subject, predicate, null));
                    }
                });

                return result;
            } catch (RepositoryException e) {
                throw new MatOntoException("Error in repository connection.", e);
            }
        }
        throw new MatOntoException("One or both of the commit IRIs could not be found in the Repository.");
    }

    @Override
    public Difference getDiff(Model original, Model changed) {
        Model originalCopy = mf.createModel(original);
        Model changedCopy = mf.createModel(changed);
        original.forEach(statement -> {
            Resource subject = statement.getSubject();
            IRI predicate = statement.getPredicate();
            Value object = statement.getObject();
            if (changedCopy.contains(subject, predicate, object)) {
                originalCopy.remove(subject, predicate, object);
                changedCopy.remove(subject, predicate, object);
            }
        });

        return new SimpleDifference.Builder().additions(changedCopy).deletions(originalCopy).build();
    }

    /**
     * Creates a conflict using the provided parameters as the data to construct it.
     *
     * @param subject The Resource identifying the conflicted statement's subject.
     * @param predicate The IRI identifying the conflicted statement's predicate.
     * @param original The Model of the original item.
     * @param left The Model of the left item being compared.
     * @param leftDeletions The Model of the deleted statements from the left Model.
     * @param right The Model of the right item being compared.
     * @param rightDeletions The Model of the deleted statements from the right Model.
     * @return A Conflict created using all of the provided data.
     */
    private Conflict createConflict(Resource subject, IRI predicate, Model original, Model left,
            Model leftDeletions, Model right, Model rightDeletions) {
        Difference leftDifference = new SimpleDifference.Builder()
                .additions(mf.createModel(left).filter(subject, predicate, null))
                .deletions(mf.createModel(leftDeletions).filter(subject, predicate, null)).build();

        Difference rightDifference = new SimpleDifference.Builder()
                .additions(mf.createModel(right).filter(subject, predicate, null))
                .deletions(mf.createModel(rightDeletions).filter(subject, predicate, null)).build();

        return new SimpleConflict.Builder(mf.createModel(original).filter(subject, predicate, null),
                vf.createIRI(subject.stringValue())).leftDifference(leftDifference).rightDifference(rightDifference)
                        .build();
    }

    /**
     * Gets the Object identified by the provided factory if it is available and satisfies the provided condition.
     *
     * @param condition The boolean identifying if the resource is correct.
     * @param id The Resource identifying which Object you are trying to get.
     * @param factory The OrmFactory which will create the desired Object.
     * @return An Optional containing the Object identified, if available.
     */
    private <T extends Thing> Optional<T> getObject(boolean condition, Resource id, OrmFactory<T> factory)
            throws MatOntoException {
        if (condition) {
            try (RepositoryConnection conn = repository.getConnection()) {
                Model model = mf.createModel();
                RepositoryResult<Statement> statements = conn.getStatements(null, null, null, id);
                statements.forEach(model::add);
                if (model.size() != 0) {
                    return Optional.of(factory.getExisting(id, model));
                }
            } catch (RepositoryException e) {
                throw new MatOntoException("Error in repository connection.", e);
            }
        }
        return Optional.empty();
    }

    /**
     * Adds the model for a Catalog to the repository which contains the provided metadata using the provided Resource
     * as the context.
     *
     * @param catalogId The Resource identifying the Catalog you wish you create.
     * @param title The title text.
     * @param description The description text.
     */
    private void addCatalogToRepo(Resource catalogId, String title, String description) {
        try (RepositoryConnection conn = repository.getConnection()) {
            OffsetDateTime now = OffsetDateTime.now();

            Catalog catalog = catalogFactory.createNew(catalogId);
            catalog.setProperty(vf.createLiteral(title), vf.createIRI(DCTERMS.TITLE.stringValue()));
            catalog.setProperty(vf.createLiteral(description), vf.createIRI(DCTERMS.DESCRIPTION.stringValue()));
            catalog.setProperty(vf.createLiteral(now), vf.createIRI(DCTERMS.ISSUED.stringValue()));
            catalog.setProperty(vf.createLiteral(now), vf.createIRI(DCTERMS.MODIFIED.stringValue()));

            conn.add(catalog.getModel(), catalogId);
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection.", e);
        }
    }

    /**
     * Gets the pre-existing catalog using the provided IRI.
     *
     * @param catalogId The Resource identifying the Catalog that the user wishes to get back.
     * @return The Catalog identified by the provided IRI.
     * @throws MatOntoException if RepositoryConnection has a problem or the catalog could not be found.
     */
    private Catalog getCatalog(Resource catalogId) throws MatOntoException {
        if (resourceExists(catalogId, Catalog.TYPE)) {
            try (RepositoryConnection conn = repository.getConnection()) {
                Model catalogModel = mf.createModel();
                RepositoryResult<Statement> statements = conn.getStatements(catalogId, null, null, catalogId);
                statements.forEach(catalogModel::add);
                return catalogFactory.getExisting(catalogId, catalogModel);
            } catch (RepositoryException e) {
                throw new MatOntoException("Error in repository connection", e);
            }
        }
        throw new MatOntoException("The catalog could not be retrieved.");
    }

    /**
     * Checks to see if the provided Resource exists in the Repository.
     *
     * @param resourceIRI The Resource to look for in the Repository
     * @return True if the Resource is in the Repository; otherwise, false.
     */
    private boolean resourceExists(Resource resourceIRI) throws MatOntoException {
        try (RepositoryConnection conn = repository.getConnection()) {
            return resourceExists(resourceIRI, conn);
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection.", e);
        }
    }

    /**
     * Checks to see if the provided Resource exists as a context in the Repository.
     *
     * @param resourceIRI The Resource context to look for in the Repository.
     * @param conn The RepositoryConnection to use for lookup.
     * @return True if the Resource is in the Repository as a context for statements; otherwise, false.
     * @throws RepositoryException If there is a problem communicating with the Repository.
     */
    private boolean resourceExists(Resource resourceIRI, RepositoryConnection conn) throws RepositoryException {
        return conn.getStatements(null, null, null, resourceIRI).hasNext();
    }

    /**
     * Checks to see if the provided Resource exists in the Repository and is of the provided type.
     *
     * @param resourceIRI The Resource to look for in the Repository
     * @param type The String of the IRI identifying the type of entity in the Repository.
     * @return True if the Resource is in the Repository; otherwise, false.
     */
    private boolean resourceExists(Resource resourceIRI, String type) throws MatOntoException {
        try (RepositoryConnection conn = repository.getConnection()) {
            return resourceExists(resourceIRI, type, conn);
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection.", e);
        }
    }

    /**
     * Checks to see if the provided Resource exists in the Repository and is of the provided type.
     *
     * @param resourceIRI The Resource to look for in the Repository.
     * @param type The String of the IRI identifying the type of entity in the Repository.
     * @param conn The RepositoryConnection to use for lookup.
     * @return True if the Resource is in the Repository; otherwise, false.
     * @throws RepositoryException If there is a problem communicating with the Repository.
     */
    private boolean resourceExists(Resource resourceIRI, String type, RepositoryConnection conn)
            throws RepositoryException {
        return conn.getStatements(null, vf.createIRI(RDF.TYPE.stringValue()), vf.createIRI(type), resourceIRI)
                .hasNext();
    }

    /**
     * Creates the base for the sorting options Object.
     */
    private void createSortingOptions() {
        sortingOptions.put(vf.createIRI(DCTERMS.MODIFIED.stringValue()), "modified");
        sortingOptions.put(vf.createIRI(DCTERMS.ISSUED.stringValue()), "issued");
        sortingOptions.put(vf.createIRI(DCTERMS.TITLE.stringValue()), "title");
    }

    /**
     * Adds the properties provided by the parameters to the provided Record.
     *
     * @param record The Record to add the properties to.
     * @param config The RecordConfig which contains the properties to set.
     * @param issued The OffsetDateTime of when the Record was issued.
     * @param modified The OffsetDateTime of when the Record was modified.
     * @param <T> An Object which extends the Record class.
     * @return T which contains all of the properties provided by the parameters.
     */
    private <T extends Record> T addPropertiesToRecord(T record, RecordConfig config, OffsetDateTime issued,
            OffsetDateTime modified) {
        record.setProperty(vf.createLiteral(config.getTitle()), vf.createIRI(DCTERMS.TITLE.stringValue()));
        record.setProperty(vf.createLiteral(issued), vf.createIRI(DCTERMS.ISSUED.stringValue()));
        record.setProperty(vf.createLiteral(modified), vf.createIRI(DCTERMS.MODIFIED.stringValue()));
        record.setProperties(config.getPublishers().stream().map(User::getResource).collect(Collectors.toSet()),
                vf.createIRI(DCTERMS.PUBLISHER.stringValue()));
        record.setProperty(vf.createLiteral(config.getIdentifier()),
                vf.createIRI(DCTERMS.IDENTIFIER.stringValue()));
        if (config.getDescription() != null) {
            record.setProperty(vf.createLiteral(config.getDescription()),
                    vf.createIRI(DCTERMS.DESCRIPTION.stringValue()));
        }
        if (config.getKeywords() != null) {
            record.setKeyword(config.getKeywords().stream().map(vf::createLiteral).collect(Collectors.toSet()));
        }
        return record;
    }

    /**
     * Creates a Record from the data provided in the BindingSet and Resource getting additional information from the
     * RepositoryConnection if necessary.
     *
     * @param bindingSet The BindingSet which contains the information about the Record.
     * @param resource The Resource which identifies the created Record.
     * @return A Record created from the provided information.
     */
    private Record processRecordBindingSet(BindingSet bindingSet, Resource resource) {
        String title = Bindings.requiredLiteral(bindingSet, "title").stringValue();
        String identifier = Bindings.requiredLiteral(bindingSet, "identifier").stringValue();

        Set<User> publishers = new HashSet<>();
        bindingSet.getBinding("publisher").ifPresent(binding -> {
            String[] values = StringUtils.split(binding.getValue().stringValue(), ",");

            for (String value : values) {
                publishers.add(userFactory.createNew(vf.createIRI(value)));
            }
        });

        RecordConfig.Builder builder = new RecordConfig.Builder(title, identifier, publishers);

        bindingSet.getBinding("description")
                .ifPresent(binding -> builder.description(binding.getValue().stringValue()));

        bindingSet.getBinding("keywords").ifPresent(binding -> builder
                .keywords(new HashSet<>(Arrays.asList(StringUtils.split(binding.getValue().stringValue(), ",")))));

        OffsetDateTime issued = Bindings.requiredLiteral(bindingSet, "issued").dateTimeValue();
        OffsetDateTime modified = Bindings.requiredLiteral(bindingSet, "modified").dateTimeValue();

        Record record = recordFactory.createNew(resource);
        bindingSet.getBinding("types").ifPresent(binding -> {
            String[] values = StringUtils.split(binding.getValue().stringValue(), ",");

            for (String value : values) {
                record.getModel().add(resource, vf.createIRI(RDF.TYPE.stringValue()), vf.createIRI(value));
            }
        });

        return addPropertiesToRecord(record, builder.build(), issued, modified);
    }

    /**
     * Adds the provided Distribution to the identified Resource adding a triple based on the provided predicate.
     *
     * @param distribution The Distribution to add to the Repository.
     * @param resourceId The Resource identified to get the Distribution added to it.
     * @param predicate The String containing the predicate for the new statement.
     * @return True if the Distribution was successfully added; otherwise, false.
     */
    private boolean addDistribution(Distribution distribution, Resource resourceId, String predicate)
            throws MatOntoException {
        if (!resourceExists(distribution.getResource(), Distribution.TYPE)
                && (resourceExists(resourceId, Version.TYPE)
                        || resourceExists(resourceId, UnversionedRecord.TYPE))) {
            try (RepositoryConnection conn = repository.getConnection()) {
                conn.begin();
                conn.add(resourceId, vf.createIRI(predicate), distribution.getResource(), resourceId);
                conn.add(distribution.getModel(), distribution.getResource());
                conn.commit();
            } catch (RepositoryException e) {
                throw new MatOntoException("Error in repository connection", e);
            }
            return true;
        }
        return false;
    }

    /**
     * Removes the Object identified by the Resource from the Resource identified to be removed from and removes
     * the Object statement using the provided String predicate.
     *
     * @param objectId The Resource identifying the Object to remove.
     * @param removeFromId The Resource identifying which Object to remove the Distribution from.
     * @param predicate The String identifying the predicate for the statement that needs to be removed.
     * @return True if the Distribution was successfully removed; otherwise, false.
     */
    private boolean removeObjectWithRelationship(Resource objectId, Resource removeFromId, String predicate)
            throws MatOntoException {
        try (RepositoryConnection conn = repository.getConnection()) {
            IRI relationshipIRI = vf.createIRI(predicate);
            boolean hasRelationship = conn.getStatements(removeFromId, relationshipIRI, objectId, removeFromId)
                    .hasNext();
            if (resourceExists(objectId) && resourceExists(removeFromId) && hasRelationship) {
                conn.begin();
                conn.clear(objectId);
                conn.remove(removeFromId, relationshipIRI, objectId, removeFromId);
                conn.commit();
                return true;
            } else {
                return false;
            }
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection", e);
        }
    }

    /**
     * Updates the Resource which is identified using the provided Model.
     *
     * @param resourceId The Resource identifying the Object that you wish to update.
     * @param model The Model containing the underlying information about the Object you are updating.
     */
    private void update(Resource resourceId, Model model) throws MatOntoException {
        try (RepositoryConnection conn = repository.getConnection()) {
            conn.begin();
            conn.clear(resourceId);
            conn.add(model, resourceId);
            conn.commit();
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection", e);
        }
    }

    /**
     * Removes the Resource which is identified.
     *
     * @param resourceId The Resource identifying the element to be removed.
     * @throws MatOntoException if RepositoryConnection has a problem.
     */
    private void remove(Resource resourceId) throws MatOntoException {
        try (RepositoryConnection conn = repository.getConnection()) {
            conn.clear(resourceId);
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection", e);
        }
    }

    /**
     * Removes the UnversionedRecord created from the provided Record along with all associated Distributions.
     *
     * @param record The Record to remove.
     * @throws MatOntoException if RepositoryConnection has a problem.
     */
    private void removeUnversionedRecord(Record record) throws MatOntoException {
        UnversionedRecord unversionedRecord = unversionedRecordFactory.getExisting(record.getResource(),
                record.getModel());
        unversionedRecord.getUnversionedDistribution().forEach(distribution -> remove(distribution.getResource()));
        remove(unversionedRecord.getResource());
    }

    /**
     * Removes the VersionedRecord created from the provided Record along with all associated Versions and all the
     * Distributions associated with those Versions.
     *
     * @param record The Record to remove.
     * @throws MatOntoException if RepositoryConnection has a problem.
     */
    private void removeVersionedRecord(Record record) throws MatOntoException {
        VersionedRecord versionedRecord = versionedRecordFactory.getExisting(record.getResource(),
                record.getModel());
        versionedRecord.getVersion()
                .forEach(version -> removeVersion(version.getResource(), versionedRecord.getResource()));
        remove(versionedRecord.getResource());
    }

    /**
     * Removes the VersionedRDFRecord created from the provided Record along with all associated Versions, all the
     * Distributions associated with those Versions, all associated Branches, and all the Commits associated with those
     * Branches.
     *
     * @param record The Record to remove.
     * @throws MatOntoException if RepositoryConnection has a problem.
     */
    private void removeVersionedRDFRecord(Record record) throws MatOntoException {
        try (RepositoryConnection conn = repository.getConnection()) {
            VersionedRDFRecord versionedRDFRecord = versionedRDFRecordFactory.getExisting(record.getResource(),
                    record.getModel());
            versionedRDFRecord.getVersion()
                    .forEach(version -> removeVersion(version.getResource(), versionedRDFRecord.getResource()));
            conn.remove(versionedRDFRecord.getResource(), vf.createIRI(VersionedRDFRecord.masterBranch_IRI), null,
                    versionedRDFRecord.getResource());
            versionedRDFRecord.getBranch()
                    .forEach(branch -> removeBranch(branch.getResource(), versionedRDFRecord.getResource()));
            remove(versionedRDFRecord.getResource());
        } catch (RepositoryException e) {
            throw new MatOntoException("Error in repository connection", e);
        }
    }

    /**
     * Gets the Resource identifying the graph that contain the additions statements.
     *
     * @param commitId The Resource identifying the Commit that have the additions.
     * @param conn The RepositoryConnection to be used to get the Resource from.
     * @return The Resource for the additions graph.
     */
    private Resource getAdditionsResource(Resource commitId, RepositoryConnection conn) {
        return (Resource) conn.getStatements(null, vf.createIRI(Revision.additions_IRI), null, commitId).next()
                .getObject();
    }

    /**
     * Gets the Resource identifying the graph that contain the deletions statements.
     *
     * @param commitId The Resource identifying the Commit that have the deletions.
     * @param conn The RepositoryConnection to be used to get the Resource from.
     * @return The Resource for the deletions graph.
     */
    private Resource getDeletionsResource(Resource commitId, RepositoryConnection conn) {
        return (Resource) conn.getStatements(null, vf.createIRI(Revision.deletions_IRI), null, commitId).next()
                .getObject();
    }

    /**
     * Adds the statements from the Revision associated with the Commit identified by the provided Resource to the
     * provided Model using the RepositoryConnection to get the statements from the repository.
     *
     * @param model The Model to update.
     * @param commitId The Resource identifying the Commit.
     * @param conn The RepositoryConnection to query the repository.
     * @return A Model with the proper statements added.
     */
    private Model addRevisionStatementsToModel(Model model, Resource commitId, RepositoryConnection conn) {
        Resource additionsId = getAdditionsResource(commitId, conn);
        Resource deletionsId = getDeletionsResource(commitId, conn);
        conn.getStatements(null, null, null, additionsId).forEach(statement -> {
            Resource subject = statement.getSubject();
            IRI predicate = statement.getPredicate();
            Value object = statement.getObject();
            if (!model.contains(subject, predicate, object)) {
                model.add(subject, predicate, object);
            }
        });
        conn.getStatements(null, null, null, deletionsId).forEach(statement -> {
            Resource subject = statement.getSubject();
            IRI predicate = statement.getPredicate();
            Value object = statement.getObject();
            if (model.contains(subject, predicate, object)) {
                model.remove(subject, predicate, object);
            } else {
                model.add(subject, predicate, object, vf.createIRI(DELETION_CONTEXT));
            }
        });
        return model;
    }

    /**
     * Gets an iterator which contains all of the Resources (commits) leading up to the provided Resource identifying a
     * commit.
     *
     * @param commitId The Resource identifying the commit that you want to get the chain for.
     * @param conn The RepositoryConnection which will be queried for the Commits.
     * @return Iterator of Values containing the requested commits.
     */
    private Iterator<Value> getCommitChainIterator(Resource commitId, RepositoryConnection conn) {
        TupleQuery query = conn.prepareTupleQuery(GET_COMMIT_CHAIN);
        query.setBinding(COMMIT_BINDING, commitId);
        TupleQueryResult result = query.evaluate();
        LinkedList<Value> commits = new LinkedList<>();
        result.forEach(bindingSet -> bindingSet.getBinding(PARENT_BINDING)
                .ifPresent(binding -> commits.add(binding.getValue())));
        commits.addFirst(commitId);
        return commits.descendingIterator();
    }

    /**
     * Builds the Model based on the provided Iterator and Resource.
     *
     * @param iterator The Iterator of commits which are supposed to be contained in the Model.
     * @param conn The RepositoryConnection which contains the requested Commits.
     * @return The Model containing the summation of all the Commits statements.
     */
    private Model createModelFromIterator(Iterator<Value> iterator, RepositoryConnection conn) {
        Model model = mf.createModel();
        iterator.forEachRemaining(value -> addRevisionStatementsToModel(model, (Resource) value, conn));
        return model;
    }
}