org.eclipse.rdf4j.spring.dao.RDF4JCRUDDao Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rdf4j-spring Show documentation
Show all versions of rdf4j-spring Show documentation
Spring integration for RDF4J
The newest version!
/*******************************************************************************
* Copyright (c) 2021 Eclipse RDF4J contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.spring.dao;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.spring.dao.exception.IncorrectResultSetSizeException;
import org.eclipse.rdf4j.spring.dao.support.bindingsBuilder.BindingsBuilder;
import org.eclipse.rdf4j.spring.dao.support.bindingsBuilder.MutableBindings;
import org.eclipse.rdf4j.spring.dao.support.key.CompositeKey;
import org.eclipse.rdf4j.spring.dao.support.opbuilder.TupleQueryEvaluationBuilder;
import org.eclipse.rdf4j.spring.dao.support.opbuilder.UpdateExecutionBuilder;
import org.eclipse.rdf4j.spring.dao.support.sparql.NamedSparqlSupplier;
import org.eclipse.rdf4j.spring.support.RDF4JTemplate;
/**
* Base class for DAOs providing CRUD functionality. The class allows for entities to be represented with different
* classes for read (ENTITY type) vs write (INPUT type) operations. DAOs that do not require this distinction must use
* the same class for both parameters.
*
* @param
* @param
* @param
* @author Florian Kleedorfer
* @since 4.0.0
*/
public abstract class RDF4JCRUDDao extends RDF4JDao {
private static final String KEY_READ_QUERY = "readQuery";
public static final String KEY_PREFIX_INSERT = "insert";
public static final String KEY_PREFIX_UPDATE = "update";
private final Class idClass;
/**
* Constructor that provides the type of the ID to the base implementation. This constructor has to be used if the
* ID is anything but IRI.
*/
public RDF4JCRUDDao(RDF4JTemplate rdf4JTemplate, Class idClass) {
super(rdf4JTemplate);
this.idClass = idClass;
}
/**
* Constructor to be used by implementations that use IRI for the ID type.
*/
public RDF4JCRUDDao(RDF4JTemplate rdf4JTemplate) {
this(rdf4JTemplate, (Class) IRI.class);
}
/**
* Saves the entity, loads it again and returns it. If the modified entity is not required, clients should prefer
* {@link #saveAndReturnId(Object, Object)} or {@link #saveAndReturnId(Object)}
*/
public final ENTITY save(INPUT input) {
ID id = getInputId(input);
final ID finalId = saveAndReturnId(input, id);
return getById(finalId);
}
public ID saveAndReturnId(INPUT input) {
return saveAndReturnId(input, getInputId(input));
}
/**
* Saves the entity and returns its (possibly newly generated) ID.
*
* @param input the entity
* @param id the id or null for a new entity.
* @return the id (a newly generated one if the specified id
is null, otherwise just id
.
*/
public ID saveAndReturnId(INPUT input, ID id) {
if (id != null) {
// delete triples for the modify case
deleteForUpdate(id);
}
final ID finalId = getOrGenerateId(id);
getInsertQueryOrUseCached(input)
.withBindings(bindingsBuilder -> populateIdBindings(bindingsBuilder, finalId))
.withBindings(bindingsBuilder -> populateBindingsForUpdate(bindingsBuilder, input))
.execute(bindings -> postProcessUpdate(input, bindings));
return finalId;
}
/**
* When updating an entity via {@link #save(Object)}, its triples are removed first using this method. The default
* implementation used {@link RDF4JTemplate#deleteTriplesWithSubject(IRI)}. If more complex deletion behaviour (e.g.
* cascading) is needed, this method should be overriden.
*/
protected void deleteForUpdate(ID id) {
IRI iri = convertIdToIri(id);
getRdf4JTemplate().deleteTriplesWithSubject(iri);
}
private ID getOrGenerateId(ID id) {
boolean idPresent;
if (id instanceof CompositeKey) {
idPresent = ((CompositeKey) id).isPresent();
} else {
idPresent = id != null;
}
if (!idPresent) {
id = generateNewId(id);
}
return id;
}
/**
* Converts the provided id to an IRI. The default implementation only works for DAOs that use IRI ids.
*
* @param id
* @return
*/
protected IRI convertIdToIri(ID id) {
if (id == null) {
return null;
}
if (idClass.equals(IRI.class)) {
return (IRI) id;
}
throw new UnsupportedOperationException(
"Cannot generically convert IDs to IRIs. The subclass must implement convertToIri(ID)");
}
/**
* Generates a new id for an entity. The default implementation only works for IRI ids.
*
* @param providedId
* @return a new id.
*/
protected ID generateNewId(ID providedId) {
if (idClass.equals(IRI.class)) {
return (ID) getRdf4JTemplate().getNewUUID();
}
throw new UnsupportedOperationException(
"Cannot generically generate any other IDs than IRIs. The subclass must implement generateNewId(ID)");
}
private UpdateExecutionBuilder getInsertQueryOrUseCached(INPUT input) {
final NamedSparqlSupplier cs = getInsertSparql(input);
String key = KEY_PREFIX_INSERT + cs.getName();
return getRdf4JTemplate().update(this.getClass(), key, cs.getSparqlSupplier());
}
public final List list() {
return getReadQueryOrUseCached()
.evaluateAndConvert()
.toList(this::mapSolution, this::postProcessMappedSolution);
}
private TupleQueryEvaluationBuilder getReadQueryOrUseCached() {
return getRdf4JTemplate().tupleQuery(getClass(), KEY_READ_QUERY, this::getReadQuery);
}
/**
* Obtains the entity with the specified id, throwing an exception if none is found.
*
* @param id the id
* @return the entity
* @throws IncorrectResultSetSizeException if no entity is found with the specified id
*/
public final ENTITY getById(ID id) {
return getByIdOptional(id)
.orElseThrow(
() -> new IncorrectResultSetSizeException(
"Expected to find exactly one entity but found 0", 1, 0));
}
/**
* Obtains an optional entity with the specified id.
*
* @param id the id
* @return an Optional maybe containing the entity
*/
public final Optional getByIdOptional(ID id) {
return getReadQueryOrUseCached()
.withBindings(bindingsBuilder -> populateIdBindings(bindingsBuilder, id))
.evaluateAndConvert()
.toSingletonOptional(this::mapSolution, this::postProcessMappedSolution);
}
/**
* Naive implementation using {@link RDF4JTemplate#delete(IRI)}. DAOs that need more complex deletion behaviour
* (e.g. cascading) should override this method.
*/
public void delete(ID id) {
if (idClass.equals(IRI.class)) {
getRdf4JTemplate().delete((IRI) id);
} else {
throw new UnsupportedOperationException(
"Cannot generically delete instances that do not use IRI ids. The subclass must implement delete(ID)");
}
}
/**
* Returns the SPARQL string used to read an instance of T from the database. The base implementation will cache the
* query string, so implementations should not try to cache the query.
*/
protected String getReadQuery() {
throw new UnsupportedOperationException(
"Cannot perform generic read operation: subclass does not override getReadQuery()");
}
/**
* Map one solution of the readQuery to the type of this DAO.
*/
protected ENTITY mapSolution(BindingSet querySolution) {
throw new UnsupportedOperationException(
"Cannot perform generic read operation: subclass does not override mapSolution()");
}
/**
* Callback invoked after mapping a solution to an entity, allowing subclasses to modify the entity before returning
* it to the client.
*/
protected ENTITY postProcessMappedSolution(ENTITY entity) {
return entity;
}
/**
* Callback invoked after a successful insert/update.
*/
protected void postProcessUpdate(INPUT input, Map bindings) {
// empty default implementation
}
/**
* Returns the SPARQL string used to write an instance of T to the database. The instance to be inserted is passed
* to the function so implementations can decide which query to use based on the instance.
*/
protected NamedSparqlSupplier getInsertSparql(INPUT input) {
throw new UnsupportedOperationException(
"Cannot perform generic write operation: subclass does not override getInsertQuery()");
}
/**
* Returns the SPARQL string used to update an instance of T in the database. The instance to be updated is passed
* to the function so implementations can decide which query to use based on the instance.
*/
protected NamedSparqlSupplier getUpdateSparql(INPUT input) {
throw new UnsupportedOperationException(
"Cannot perform generic write operation: subclass does not override getUpdateQuery()");
}
/**
* Binds the instance id to query variable(s).
*/
protected abstract void populateIdBindings(MutableBindings bindingsBuilder, ID id);
/**
* Sets the non-id bindings on for the write query such that the instance of type I is written to the database. ID
* bindings are set through populateIdBindings()
*/
protected void populateBindingsForUpdate(MutableBindings bindingsBuilder, INPUT input) {
throw new UnsupportedOperationException(
"Cannot perform generic write operation: subclass does not override populateBindingsForUpdate()");
}
/**
* Obtains the id of the input instance or null if it is new (or a partially populated composite key).
*/
protected ID getInputId(INPUT input) {
throw new UnsupportedOperationException(
"Cannot perform generic write operation: subclass does not override getInputId()");
}
/**
* Returns a new BindingsBuilder for your convenience.
*/
protected static BindingsBuilder newBindingsBuilder() {
return new BindingsBuilder();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy