/*
* AbstractRDFEntityAccessor.java
*
* Created on November 1, 2006, 9:36 AM
*
* Description: Contains the common RDF entity annotation gathering methods for use in the
* subclasses for loading and persistence.
*
* Copyright (C) 2006 Stephen L. Reed.
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation; either
* version 2 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 General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.texai.kb.persistence;
import java.io.BufferedWriter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Stack;
import javax.persistence.Id;
import net.jcip.annotations.NotThreadSafe;
import org.apache.log4j.Logger;
import org.openrdf.model.BNode;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.model.impl.ValueFactoryImpl;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
import org.texai.kb.Constants;
import org.texai.util.TexaiException;
/** This abstract class contains the state and behavior that is common to the concrete subclasses RDFEntityLoader,
* RDFEntityPersister and RDFEntityRemover. It is responsible for parsing the semantic annotations from the
* entity in preparation for persist() or find() methods.
*
*
* It features a stack to contain state information during recursive
* method calls, avoiding the need to construct new instances.
*
* @author reed
*/
@NotThreadSafe
public abstract class AbstractRDFEntityAccessor { // NOPMD
/** the predicate used to persist a map entry key */
protected static final URI PERSISTENT_MAP_ENTRY_KEY_URI = new URIImpl(Constants.TERM_PERSISTENT_MAP_ENTRY_KEY);
/** the predicate used to persist a map entry value */
protected static final URI PERSISTENT_MAP_ENTRY_VALUE_URI = new URIImpl(Constants.TERM_PERSISTENT_MAP_ENTRY_VALUE);
/** the class of the RDF entity */
private Class<?> rdfEntityClass;
/** the instantiated RDF entity */
private RDFPersistent rdfEntity;
/** the array of type level annotations */
private Annotation[] typeLevelAnnotations;
/** the dictionary of field level annotations, field -> RDFProperty annotation */
private Map<Field, Annotation> fieldAnnotationDictionary;
/** the id field */
private Field idField;
/** the namespace dictionary, prefix --> namespace URI */
private Map<String, String> namespaceDictionary;
/** the super classes of this RDF entity */
private URI[] subClassOfURIs;
/** the typeURIs of this RDF entity */
private URI[] typeURIs;
/** the class URI */
private URI classURI;
/** the stack of abstract RDF entity information that allows recursive method calls */
private final Stack<Object> rdfEntityInfoStack = new Stack<>();
/** the persistence context URI */
private URI contextURI;
/** the override persistence context URI */
private URI overrideContextURI = null;
/** the effective persistence context URI */
private URI effectiveContextURI;
/** the instance URI */
private URI instanceURI;
/** the value factory */
private final ValueFactory valueFactory = new ValueFactoryImpl();
/** the dictionary of memoized class annotation information, class --> ClassAnnotationInfo */
private final Map<Class<?>, ClassAnnotationInfo> memoizedClassAnnotationInfos = new HashMap<>();
/** Creates a new instance of AbstractRDFEntityAccessor. */
protected AbstractRDFEntityAccessor() {
super();
fieldAnnotationDictionary = new HashMap<>();
namespaceDictionary = new HashMap<>();
}
/** Returns true if the given object is an annotated RDF entity.
*
* @param obj the given object
* @return true if the given object is an annotated RDF entity
*/
public final boolean isRDFEntity(final Object obj) {
//Preconditions
assert obj != null : "obj must not be null";
return isRDFEntityClass(obj.getClass());
}
/** Returns true if the given object class is an annotated RDF entity class.
*
* @param clazz the given object class
* @return true if the given object is an annotated RDF entity class
*/
@SuppressWarnings("unchecked")
public final boolean isRDFEntityClass(final Class<?> clazz) {
//Preconditions
assert clazz != null : "clazz must not be null";
for (final Annotation annotation : clazz.getAnnotations()) {
// ignore whether classes have differnt classloaders
if (annotation.annotationType().getName().equals(RDFEntity.class.getName())) {
return true;
}
}
return false;
}
/** Gathers annotations for the RDF entity class. */
protected final void gatherAnnotationsForRDFEntityClass() {
//Preconditions
assert rdfEntityClass != null : "rdfEntityClass must not be null"; // NOPMD
final ClassAnnotationInfo classAnnotationInfo = memoizedClassAnnotationInfos.get(rdfEntityClass);
if (classAnnotationInfo == null) {
typeLevelAnnotations = rdfEntityClass.getAnnotations();
for (final Annotation annotation : typeLevelAnnotations) {
if (getLogger().isDebugEnabled()) {
getLogger().debug(stackLevel() + "type level annotation: " + annotation.toString());
}
}
gatherFieldAnnotationDictionary(rdfEntityClass);
if (idField == null) {
throw new TexaiException("cannot find @Id field in class " + rdfEntityClass.getName() + " or in its superclasses");
}
memoizedClassAnnotationInfos.put(rdfEntityClass, new ClassAnnotationInfo(
typeLevelAnnotations,
fieldAnnotationDictionary,
namespaceDictionary,
idField));
} else {
typeLevelAnnotations = classAnnotationInfo.typeLevelAnnotations;
fieldAnnotationDictionary = classAnnotationInfo.fieldAnnotationDictionary;
idField = classAnnotationInfo.idField;
namespaceDictionary = classAnnotationInfo.namespaceDictionary;
}
}
/** Processes the type level annotations to configure the RDF entity settings */
protected final void configureRDFEntitySettings() {
//Preconditions
assert typeLevelAnnotations != null : "typeLevelAnnotations must not be null";
assert rdfEntityClass != null : "rdfEntityClass must not be null";
subClassOfURIs = new URI[0];
for (final Annotation annotation : typeLevelAnnotations) {
if (annotation instanceof RDFEntity) {
final RDFEntity rdfEntity1 = (RDFEntity) annotation;
for (final RDFNamespace rdfNamespace : rdfEntity1.namespaces()) {
namespaceDictionary.put(rdfNamespace.prefix(), rdfNamespace.namespaceURI());
}
// ensure that the reserved namespace definitions are present
namespaceDictionary.put("rdf", Constants.RDF_NAMESPACE);
namespaceDictionary.put("rdfs", Constants.RDFS_NAMESPACE);
namespaceDictionary.put("owl", Constants.OWL_NAMESPACE);
namespaceDictionary.put("cyc", Constants.CYC_NAMESPACE);
namespaceDictionary.put("texai", Constants.TEXAI_NAMESPACE);
if (rdfEntity1.context().isEmpty()) {
throw new TexaiException("context annotation property is missing");
} else {
contextURI = makeURI(rdfEntity1.context());
if (overrideContextURI == null) {
effectiveContextURI = contextURI;
} else {
effectiveContextURI = overrideContextURI;
}
}
if (rdfEntity1.subject().isEmpty()) {
classURI = makeURI(getRDFEntityClass().getName());
} else {
classURI = makeURI(rdfEntity1.subject());
if (getLogger().isDebugEnabled()) {
getLogger().debug(stackLevel() + "class URI: " + classURI.toString());
}
}
final int subClassOfs_len = rdfEntity1.subClassOf().length;
subClassOfURIs = new URI[subClassOfs_len];
for (int i = 0; i < subClassOfs_len; i++) {
subClassOfURIs[i] = makeURI(rdfEntity1.subClassOf()[i]);
}
final int types_len = rdfEntity1.type().length;
typeURIs = new URI[types_len];
for (int i = 0; i < types_len; i++) {
typeURIs[i] = makeURI(rdfEntity1.type()[i]);
}
break;
}
}
if (getLogger().isDebugEnabled()) {
getLogger().debug(stackLevel() + "default context: " + contextURI.toString());
if (overrideContextURI != null) {
getLogger().debug(stackLevel() + "override context: " + overrideContextURI.toString());
}
}
for (final URI subClassOf : subClassOfURIs) {
if (getLogger().isDebugEnabled()) {
getLogger().debug(stackLevel() + "subClassOf: " + subClassOf.toString());
}
}
//Postconditions
assert effectiveContextURI != null : "effectiveContextURI must not be null for class " + rdfEntityClass + ", instanceURI " + getInstanceURI();
if (classURI == null) {
throw new TexaiException(rdfEntityClass + " effectiven context URI cannot be determined");
}
}
/** Gets the class URI from its RDF type level annotations.
*
* @param clazz the class
* @return the class URI from its RDF type level annotations
*/
protected final URI getClassURI(final Class<?> clazz) {
//Preconditions
assert rdfEntityClass != null : "rdfEntityClass must not be null";
final Annotation[] typeLevelAnnotations1 = rdfEntityClass.getAnnotations();
for (final Annotation annotation : typeLevelAnnotations1) {
if (getLogger().isDebugEnabled()) {
getLogger().debug(stackLevel() + "type level annotation: " + annotation.toString());
}
}
for (final Annotation annotation : typeLevelAnnotations1) {
if (annotation instanceof RDFEntity) {
final RDFEntity rdfEntity1 = (RDFEntity) annotation;
URI classURI1;
if (rdfEntity1.subject().isEmpty()) {
classURI1 = makeURI(rdfEntityClass.getName());
} else {
classURI1 = makeURI(rdfEntity1.subject());
}
if (getLogger().isDebugEnabled()) {
getLogger().debug(stackLevel() + "class URI: " + classURI1.toString());
}
break;
}
}
//Postconditions
if (classURI == null) {
throw new TexaiException(clazz.getName() + " is not an RDF entity");
}
return classURI;
}
/** Gathers the field level annotations for the given class and its superclasses, creating the
* dictionary, field -> RDFProperty annotation. Also locates the id field.
*
* @param clazz the given class
*/
private void gatherFieldAnnotationDictionary(final Class<?> clazz) {
//Preconditions
assert clazz != null : "clazz must not be null";
if (!clazz.getName().equals(Object.class.getName()) && clazz.getSuperclass() != null) {
// work down the class hierarchy
gatherFieldAnnotationDictionary(clazz.getSuperclass());
}
if (getLogger().isDebugEnabled()) {
getLogger().debug(stackLevel() + "gathering field annotations for class: " + clazz.getName());
}
final Field[] fields = clazz.getDeclaredFields();
for (final Field field : fields) {
if (getAnnotation(field, Id.class) == null) {
final Annotation annotation = getAnnotation(field, RDFProperty.class);
if (annotation != null) {
if (getLogger().isDebugEnabled()) {
getLogger().debug(stackLevel() + " field: " + field.getName() + " annotation: " + annotation);
}
fieldAnnotationDictionary.put(field, annotation);
}
} else {
if (getLogger().isDebugEnabled()) {
getLogger().debug(stackLevel() + " id field: " + field.getName());
}
idField = field;
}
}
}
/** Returns the given annotation from the given field, or null if not found. This implementation ignores whether
* the annotation class and the given class have different classloaders as might happen in the OSGi framework.
*
* @param field the given field
* @param clazz the given annotation class
* @return the given annotation from the given field, or null if not found
*/
private Annotation getAnnotation(final Field field, final Class<?> clazz) {
//Preconditions
assert field != null : "field must not be null";
assert clazz != null : "clazz must not be null";
final String className = clazz.getName();
for (final Annotation annotation : field.getDeclaredAnnotations()) {
if (annotation.annotationType().getName().equals(className)) {
return annotation;
}
}
return null;
}
/** Returns a URI formed from the given name, prepending a namespace if required.
*
* @param name the given name, which may include a namespace prefix
* @return a URI formed from the given name, prepending a namespace if required
*/
protected final URI makeURI(final String name) {
//Preconditions
assert name != null : "name must not be null";
assert !name.isEmpty() : "name must not be empty";
URI uri;
if (name.indexOf('/') > -1 || name.indexOf('#') > -1) {
uri = getValueFactory().createURI(name);
} else {
final int index = name.indexOf(':');
final String prefix;
if (index == -1) {
prefix = "texai";
} else {
prefix = name.substring(0, index);
}
final String namespaceURI = namespaceDictionary.get(prefix);
if (namespaceURI == null) {
throw new TexaiException(name + " is a malformed URI, cannot find a namespace for the prefix " + prefix
+ "\nin: " + namespaceDictionary);
}
final String unprefixedName = name.substring(index + 1);
if (unprefixedName.isEmpty()) {
throw new TexaiException(name + " is a malformed URI, cannot parse prefixed name");
}
uri = getValueFactory().createURI(namespaceURI, unprefixedName);
}
//Postconditions
assert uri != null : "uri must not be null";
return uri;
}
/** Gets a default property URI for the annotated field.
*
* @param field the annotated field
* @param rdfProperty the property annotation
* @return a default property URI
*/
protected URI getEffectivePropertyURI(final Field field, final RDFProperty rdfProperty) {
//Preconditions
assert field != null : "field must not be null";
assert rdfProperty != null : "rdfProperty must not be null";
if (!rdfProperty.predicate().isEmpty()) {
return makeURI(rdfProperty.predicate());
}
return RDFUtility.getDefaultPropertyURI(
field.getDeclaringClass().getName(), // className
field.getName(), // fieldName
field.getType()); // fieldType
}
/** Gets the logger.
*
* @return the logger
*/
abstract Logger getLogger();
/** Gets the value factory.
*
* @return the value factory
*/
public final ValueFactory getValueFactory() {
return valueFactory;
}
/** Gets the RDF entity class.
*
* @return the RDF entity class
*/
public final Class<?> getRDFEntityClass() {
return rdfEntityClass;
}
/** Sets the RDF entity class.
*
* @param rdfEntityClass the RDF entity class
*/
public final void setRDFEntityClass(final Class<?> rdfEntityClass) {
//Preconditions
assert rdfEntityClass != null : "rdfEntityClass must not be null";
this.rdfEntityClass = rdfEntityClass;
}
/** Gets the instantiated RDF entity.
*
* @return the instantiated RDF entity
*/
public final RDFPersistent getRDFEntity() {
return rdfEntity;
}
/** Sets the instantiated RDF entity.
*
* @param rdfEntity the instantiated RDF entity
*/
public final void setRDFEntity(final RDFPersistent rdfEntity) {
this.rdfEntity = rdfEntity;
}
/** Gets the array of type level annotations.
*
* @return the array of type level annotations
*/
protected final Annotation[] getTypeLevelAnnotations() {
return typeLevelAnnotations; // NOPMD
}
/** Gets the dictionary of field level annotations.
*
* @return the dictionary of field level annotations, field -> Id or RDFProperty annotation
*/
public final Map<Field, Annotation> getFieldAnnotationDictionary() {
return fieldAnnotationDictionary;
}
/** Gets the super classes of this RDF entity.
*
* @return the super classes of this RDF entity
*/
protected final URI[] getSubClassOfURIs() {
return subClassOfURIs; // NOPMD
}
/**
* Sets the super classes of this RDF entity.
*
* @param subClassOfURIs the super classes of this RDF entity
*/
protected final void setSubClassOfURIs(final URI[] subClassOfURIs) {
this.subClassOfURIs = subClassOfURIs;
}
/**
* Gets the type URIs of this RDF entity.
*
* @return the type URIS of this RDF entity
*/
protected final URI[] getTypeURIs() {
return typeURIs; // NOPMD
}
/**
* Sets the typeURIs of this RDF entity.
*
*
* @param typeURIs the typeURIs of this RDF entity
*/
protected final void setTypeURIs(final URI[] typeURIs) {
this.typeURIs = typeURIs;
}
/** Gets the class URI.
*
* @return the class URI for this RDF entity
*/
public final URI getClassURI() {
return classURI;
}
/**
* Sets the class URI.
*
* @param classURI the class URI for this RDF entity
*/
public final void setClassURI(final URI classURI) {
//Preconditions
assert classURI != null : "classURI must not be null";
this.classURI = classURI;
}
/**
* Gets the context URI.
*
*
* @return the context URI
*/
public final URI getContextURI() {
return contextURI;
}
/**
* Sets the contex URI.
*
*
* @param contextURI the context URI
*/
public final void setContextURI(final URI contextURI) {
this.contextURI = contextURI;
}
/** Gets the instance URI.
*
* @return the instance URI
*/
public final URI getInstanceURI() {
return instanceURI;
}
/**
* Sets the instance URI.
*
* @param instanceURI the instance URI
*/
public final void setInstanceURI(final URI instanceURI) {
this.instanceURI = instanceURI;
}
/** Gets the id field.
*
* @return the id field
*/
public final Field getIdField() {
return idField;
}
/** Returns the stack level for logging.
*
* @return the stack level for logging.
*/
public final String stackLevel() {
//Preconditions
assert rdfEntityInfoStack != null : "rdfEntityInfoStack must not be null";
return "[" + rdfEntityInfoStack.size() + "] ";
}
/** Pushes the current session bean state onto a stack and then intitializes the session state. */
protected final void saveAbstractSessionState() {
//Preconditions
assert rdfEntityInfoStack != null : "rdfEntityInfoStack must not be null";
getLogger().debug(stackLevel() + "saving abstract session state");
rdfEntityInfoStack.push(new AbstractRDFEntityInfo(
rdfEntityClass,
rdfEntity,
typeLevelAnnotations,
fieldAnnotationDictionary,
namespaceDictionary,
contextURI,
overrideContextURI,
effectiveContextURI,
subClassOfURIs,
typeURIs,
classURI,
instanceURI,
idField));
}
/** Initializes the session bean state. */
protected final void initializeAbstractSessionState() {
rdfEntityClass = null;
rdfEntity = null;
typeLevelAnnotations = null;
fieldAnnotationDictionary = new HashMap<>();
namespaceDictionary = new HashMap<>();
subClassOfURIs = null;
typeURIs = null;
classURI = null;
overrideContextURI = null;
contextURI = null;
effectiveContextURI = null;
instanceURI = null;
idField = null;
}
/** Restores the session state following a recursive method call. */
protected final void restoreAbstractSessionState() {
//Preconditions
assert rdfEntityInfoStack != null : "rdfEntityInfoStack must not be null";
final AbstractRDFEntityInfo abstractRDFEntityInfo = (AbstractRDFEntityInfo) rdfEntityInfoStack.pop();
rdfEntityClass = abstractRDFEntityInfo.rdfEntityClass;
rdfEntity = abstractRDFEntityInfo.rdfEntity;
typeLevelAnnotations = abstractRDFEntityInfo.typeLevelAnnotations;
fieldAnnotationDictionary = abstractRDFEntityInfo.fieldAnnotationDictionary;
namespaceDictionary = abstractRDFEntityInfo.namespaceDictionary;
subClassOfURIs = abstractRDFEntityInfo.subClassOfURIs;
typeURIs = abstractRDFEntityInfo.typeURIs;
classURI = abstractRDFEntityInfo.classURI;
contextURI = abstractRDFEntityInfo.contextURI;
overrideContextURI = abstractRDFEntityInfo.overrideContextURI;
effectiveContextURI = abstractRDFEntityInfo.effectiveContextURI;
instanceURI = abstractRDFEntityInfo.instanceURI;
idField = abstractRDFEntityInfo.idField;
if (getLogger().isDebugEnabled()) {
getLogger().debug(stackLevel() + "restored abstract session state");
}
}
/** Gets the override persistence context.
*
* @return the override persistence context
*/
public final URI getOverrideContextURI() {
return overrideContextURI;
}
/** Sets the override persistence context.
*
* @param overrideContextURI the override persistence context
*/
public final void setOverrideContextURI(final URI overrideContextURI) {
this.overrideContextURI = overrideContextURI;
}
/** Gets the effective persistence context URI.
*
* @return the effective persistence context URI
*/
public final URI getEffectiveContextURI() {
return effectiveContextURI;
}
/** Sets the effective persistence context URI.
*
* @param effectiveContextURI the effective persistence context URI
*/
public final void setEffectiveContextURI(final URI effectiveContextURI) {
//Preconditions
assert effectiveContextURI != null : "effectiveContextURI must not be null";
this.effectiveContextURI = effectiveContextURI;
}
/** Contains the session bean state for recursive method calls. */
private static class AbstractRDFEntityInfo { // NOPMD
/** the RDF entity class */
private final Class<?> rdfEntityClass; // NOPMD
/** the instantiated RDF entity */
private final RDFPersistent rdfEntity; // NOPMD
/** the array of type level annotations */
private final Annotation[] typeLevelAnnotations; // NOPMD
/** the dictionary of field level annotations, field -> Id or RDFProperty annotation */
private final Map<Field, Annotation> fieldAnnotationDictionary; // NOPMD
/** the namespace dictionary, prefix --> namespace URI */
private final Map<String, String> namespaceDictionary; // NOPMD
/** the contextURI */
private final URI contextURI; // NOPMD
/** the override persistence context URI */
private final URI overrideContextURI;
/** the effective persistence context URI */
private final URI effectiveContextURI;
/** the RDF super classes of this RDF entity */
private final URI[] subClassOfURIs; // NOPMD
/** the typeURIs of this RDF entity */
private final URI[] typeURIs; // NOPMD
/** the class URI */
private final URI classURI; // NOPMD
/** the instance URI */
private final URI instanceURI; // NOPMD
/** the id field */
private final Field idField; // NOPMD
/**
* Creates a new AbstractRDFEntityInfo instance.
*
* @param rdfEntityClass the RDF entity class
* @param rdfEntity the instantiated RDF entity
* @param typeLevelAnnotations the array of type level annotations
* @param fieldAnnotationDictionary the dictionary of field level annotations, field -> Id or RDFProperty annotation
* @param namespaceDictionary the namespace dictionary, prefix --> namespace URI
* @param contextURI the context
* @param overrideContextURI the override context
* @param effectiveContextURI the effective context
* @param subClassOfURIs the super classes of this RDF entity
* @param typeURIs the types of this RDF entity
* @param classURI the class URI
* @param instanceURI the instance URI
* @param idField the id field
*/
protected AbstractRDFEntityInfo(
final Class<?> rdfEntityClass,
final RDFPersistent rdfEntity,
final Annotation[] typeLevelAnnotations,
final Map<Field, Annotation> fieldAnnotationDictionary,
final Map<String, String> namespaceDictionary,
final URI contextURI,
final URI overrideContextURI,
final URI effectiveContextURI,
final URI[] subClassOfURIs,
final URI[] typeURIs,
final URI classURI,
final URI instanceURI,
final Field idField) {
super();
this.rdfEntityClass = rdfEntityClass;
this.rdfEntity = rdfEntity;
this.typeLevelAnnotations = typeLevelAnnotations;
this.fieldAnnotationDictionary = fieldAnnotationDictionary;
this.namespaceDictionary = namespaceDictionary;
this.contextURI = contextURI;
this.overrideContextURI = overrideContextURI;
this.effectiveContextURI = effectiveContextURI;
this.typeURIs = typeURIs;
this.subClassOfURIs = subClassOfURIs;
this.classURI = classURI;
this.instanceURI = instanceURI;
this.idField = idField;
}
}
/** Contains the memoized class annotation information. */
private static class ClassAnnotationInfo {
/** the array of type level annotations */
private final Annotation[] typeLevelAnnotations; // NOPMD
/** the dictionary of field level annotations, field -> Id or RDFProperty annotation */
private final Map<Field, Annotation> fieldAnnotationDictionary; // NOPMD
/** the namespace dictionary, prefix --> namespace URI */
private final Map<String, String> namespaceDictionary; // NOPMD
/** the id field */
private final Field idField; // NOPMD
/** Constructs a new ClassAnnotationInfo instance.
*
* @param typeLevelAnnotations the array of type level annotation
* @param fieldAnnotationDictionary the dictionary of field level annotations, field -> Id or RDFProperty annotation
* @param namespaceDictionary the namespace dictionary, prefix --> namespace URI
* @param idField the id field
*/
protected ClassAnnotationInfo(
final Annotation[] typeLevelAnnotations,
final Map<Field, Annotation> fieldAnnotationDictionary,
final Map<String, String> namespaceDictionary,
final Field idField) {
//preconditions
assert typeLevelAnnotations != null : "typeLevelAnnotations must not be null";
assert fieldAnnotationDictionary != null : "fieldAnnotationDictionary must not be null";
assert idField != null : "idField must not be null";
this.typeLevelAnnotations = typeLevelAnnotations;
this.fieldAnnotationDictionary = fieldAnnotationDictionary;
this.namespaceDictionary = namespaceDictionary;
this.idField = idField;
}
}
/** Adds the given list of RDF values to the repository as an RDF list structure.
*
* @param repositoryConnection the repository connection
* @param rdfEntityManager the entity manager
* @param valueList the given list of RDF values
* @param writer the export output writer, or null when objects are ordinarily persisted to the given RDF quad store
* @return the blank node that heads the RDF list structure
*/
public final BNode addRDFList(
final RepositoryConnection repositoryConnection,
final RDFEntityManager rdfEntityManager,
final List<Value> valueList,
final BufferedWriter writer) {
//Preconditions
assert repositoryConnection != null : "repositoryConnection must not be null";
assert valueList != null : "valueList must not be null";
assert !valueList.isEmpty() : "valueList must not be empty";
assert rdfEntityManager != null : "rdfEntityManager must not be null";
BNode element = valueFactory.createBNode();
final BNode rdfListHead = element;
Statement statement;
final int sizeLessOne = valueList.size() - 1;
int index = 0;
for (; index < sizeLessOne; index++) {
// link all but the last value onto the RDF list
final Value value = valueList.get(index);
statement = valueFactory.createStatement(
element,
RDF.FIRST,
value,
getEffectiveContextURI());
if (writer == null) {
rdfEntityManager.addStatement(repositoryConnection, statement);
} else {
try {
writer.write(RDFUtility.formatStatementAsTurtle(statement));
writer.newLine();
} catch (IOException ex) {
throw new TexaiException(ex);
}
}
getLogger().info("added: " + RDFUtility.formatStatement(statement) + " to " + repositoryConnection.getRepository().getDataDir().getName());
final BNode nextElement = valueFactory.createBNode();
statement = valueFactory.createStatement(
element,
RDF.REST,
nextElement,
getEffectiveContextURI());
if (writer == null) {
rdfEntityManager.addStatement(repositoryConnection, statement);
} else {
try {
writer.write(RDFUtility.formatStatementAsTurtle(statement));
writer.newLine();
} catch (IOException ex) {
throw new TexaiException(ex);
}
}
getLogger().info("added: " + RDFUtility.formatStatement(statement) + " to " + repositoryConnection.getRepository().getDataDir().getName());
element = nextElement;
}
// the final RDF list value is linked to nil
final Value value = valueList.get(index);
statement = valueFactory.createStatement(
element,
RDF.FIRST,
value,
getEffectiveContextURI());
if (writer == null) {
rdfEntityManager.addStatement(repositoryConnection, statement);
} else {
try {
writer.write(RDFUtility.formatStatementAsTurtle(statement));
writer.newLine();
} catch (IOException ex) {
throw new TexaiException(ex);
}
}
getLogger().info("added: " + RDFUtility.formatStatement(statement) + " to " + repositoryConnection.getRepository().getDataDir().getName());
statement = valueFactory.createStatement(
element,
RDF.REST,
RDF.NIL,
getEffectiveContextURI());
if (writer == null) {
rdfEntityManager.addStatement(repositoryConnection, statement);
} else {
try {
writer.write(RDFUtility.formatStatementAsTurtle(statement));
writer.newLine();
} catch (IOException ex) {
throw new TexaiException(ex);
}
}
getLogger().info("added: " + RDFUtility.formatStatement(statement) + " to " + repositoryConnection.getRepository().getDataDir().getName());
return rdfListHead;
}
/** Returns the list of RDF values linked from the given RDF list head.
*
* @param repositoryConnection the repository connection
* @param rdfListHead the blank node that heads the RDF list
* @return the list of RDF values linked from the given RDF list head
*/
public final List<Value> getRDFListValues(
final RepositoryConnection repositoryConnection,
final BNode rdfListHead) {
//Preconditions
assert repositoryConnection != null : "repositoryConnection must not be null";
assert rdfListHead != null : "rdfListHead must not be null";
assert effectiveContextURI != null : "effectiveContextURI must not be null";
final List<Value> rdfValueList = new ArrayList<>();
getLogger().debug(stackLevel() + " getting existing RDF list values, head: " + rdfListHead);
Value[] firstAndRest = getRDFListComponents(repositoryConnection, (Resource) rdfListHead);
rdfValueList.add(firstAndRest[0]);
Value rest = firstAndRest[1];
while (!rest.equals(RDF.NIL)) {
assert rest instanceof Resource;
firstAndRest = getRDFListComponents(repositoryConnection, (Resource) rest);
rdfValueList.add(firstAndRest[0]);
rest = firstAndRest[1];
}
//Postconditions
assert !rdfValueList.isEmpty() : "rdfValueList must not be empty at " + rdfListHead;
return rdfValueList;
}
// Disabled because the lazy initialized query fails to find some valid RDF statements at a certain point.
// /** Returns the URI array [first, rest] whose elements are the first element of the given list node, and the rest of the list at the
// * given node.
// *
// * @param repositoryConnection the repository connection
// * @param listNode the blank node that heads the RDF list
// * @return the URI array [first, rest] whose elements are the first element of the given list node, and the rest of the list at the
// * given node
// */
// private Value[] getRDFListComponents(
// final RepositoryConnection repositoryConnection,
// final Resource listNode) {
// //Preconditions
// assert listNode != null : "listNode must not be null";
// assert effectiveContextURI != null : "effectiveContextURI must not be null";
//
// final Value[] firstAndRest = {null, null};
// if (rdfListElementsQuery == null) {
// // lazy initialization
// try {
// rdfListElementsQuery = repositoryConnection.prepareTupleQuery(
// QueryLanguage.SERQL,
// "SELECT f, r FROM {s} <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> {f}; <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> {r}");
// } catch (final MalformedQueryException ex) {
// throw new TexaiException(ex);
// } catch (final RepositoryException ex) {
// throw new TexaiException(ex);
// }
// }
// rdfListElementsQuery.setBinding("s", listNode);
// final TupleQueryResult tupleQueryResult;
// try {
// tupleQueryResult = rdfListElementsQuery.evaluate();
// if (tupleQueryResult.hasNext()) {
// final BindingSet bindingSet = tupleQueryResult.next();
// final Binding firstBinding = bindingSet.getBinding("f");
// if (firstBinding == null) {
// throw new TexaiException("missing RDf first list binding at listNode: " + listNode);
// } else {
// firstAndRest[0] = firstBinding.getValue();
// }
// final Binding restBinding = bindingSet.getBinding("r");
// if (restBinding == null) {
// throw new TexaiException("missing RDf rest list binding at listNode: " + listNode);
// } else {
// firstAndRest[1] = restBinding.getValue();
// }
//
// } else {
// throw new TexaiException("invalid RDF list at " + listNode);
// }
// tupleQueryResult.close();
// if (firstAndRest[0] == null || firstAndRest[1] == null) {
// throw new TexaiException("missing RDF list components at listNode: " + listNode);
// }
// } catch (final Throwable ex) {
// getLogger().error("repositoryConnection: " + repositoryConnection);
// getLogger().error("repository: " + repositoryConnection.getRepository().getDataDir());
// getLogger().error("statements having " + listNode + " as subject...");
// try {
// final RepositoryResult<Statement> repositoryResult = repositoryConnection.getStatements(listNode, null, null, false);
// while (repositoryResult.hasNext()) {
// getLogger().error("statement: " + RDFUtility.formatStatement(repositoryResult.next()));
// }
// } catch (final RepositoryException ex1) {
// // ignore
// }
// throw new TexaiException(ex);
// }
//
// //Postconditions
// assert firstAndRest.length == 2 : "firstAndRest must have length 2";
// assert firstAndRest[0] != null : "first must not be null";
// assert firstAndRest[1] != null : "rest must not be null";
//
// return firstAndRest;
// }
/** Returns the URI array [first, rest] whose elements are the first element of the given list node, and the rest of the list at the
* given node.
*
* @param repositoryConnection the repository connection
* @param listNode the blank node that heads the RDF list
* @return the URI array [first, rest] whose elements are the first element of the given list node, and the rest of the list at the
* given node
*/
private Value[] getRDFListComponents(
final RepositoryConnection repositoryConnection,
final Resource listNode) {
//Preconditions
assert listNode != null : "listNode must not be null";
assert effectiveContextURI != null : "effectiveContextURI must not be null";
final Value[] firstAndRest = {null, null};
try {
RepositoryResult<Statement> repositoryResult = repositoryConnection.getStatements(
listNode,
RDF.FIRST,
null,
false);
if (repositoryResult.hasNext()) {
firstAndRest[0] = repositoryResult.next().getObject();
}
repositoryResult.close();
repositoryResult = repositoryConnection.getStatements(
listNode,
RDF.REST,
null,
false);
if (repositoryResult.hasNext()) {
firstAndRest[1] = repositoryResult.next().getObject();
}
repositoryResult.close();
} catch (final RepositoryException ex) {
throw new TexaiException(ex);
}
if (firstAndRest[0] == null || firstAndRest[1] == null) {
throw new TexaiException("missing RDF list components at listNode: " + listNode);
}
//Postconditions
assert firstAndRest.length == 2 : "firstAndRest must have length 2";
assert firstAndRest[0] != null : "first must not be null";
assert firstAndRest[1] != null : "rest must not be null";
return firstAndRest;
}
/** Removes the given RDF list.
*
* @param repositoryConnection the repository connection
* @param rdfEntityManager the entity manager
* @param listNode the blank node that heads the RDF list
*/
protected final void removeRDFList(
final RepositoryConnection repositoryConnection,
final RDFEntityManager rdfEntityManager,
final BNode listNode) {
//Preconditions
assert repositoryConnection != null : "repositoryConnection must not be null";
assert rdfEntityManager != null : "rdfEntityManager must not be null";
assert listNode != null : "listNode must not be null";
final Value first = RDFUtility.getObjectGivenSubjectAndPredicate(
listNode, RDF.FIRST, effectiveContextURI, repositoryConnection);
if (first != null) { // expected statement may be missing if the value was previously removed, e.g. via cascadeRemove
final Statement statement = valueFactory.createStatement(
listNode,
RDF.FIRST,
first,
effectiveContextURI);
rdfEntityManager.removeStatement(repositoryConnection, statement);
getLogger().info("removed: " + RDFUtility.formatStatement(statement));
}
final Value rest = RDFUtility.getObjectGivenSubjectAndPredicate(
listNode, RDF.REST, effectiveContextURI, repositoryConnection);
final Statement statement = valueFactory.createStatement(
listNode,
RDF.REST,
rest,
effectiveContextURI);
rdfEntityManager.removeStatement(repositoryConnection, statement);
getLogger().info("removed: " + RDFUtility.formatStatement(statement));
if (rest.equals(RDF.NIL)) {
return;
}
removeRDFList(repositoryConnection, rdfEntityManager, (BNode) rest);
}
/** Provides a container for a map entry. */
protected static class MapEntry {
/** the map entry key Java object */
protected Object key;
/** the map entry key RDF value */
protected Value keyRDFValue;
/** the map entry value Java object */
protected Object value;
/** the map entry value RDF value */
protected Value valueRDFValue;
/** the blank node */
protected BNode bNode;
/** Constructs a new MapEntry object.
*
* @param bNode the blank node
* @param keyRDFValue the map entry key RDF value
* @param valueRDFValue the map entry value RDF value
*/
MapEntry(
final BNode bNode,
final Value keyRDFValue,
final Value valueRDFValue) {
//Preconditions
assert bNode != null : "bNode must not be null";
assert keyRDFValue != null : "keyRDFValue must not be null";
assert valueRDFValue != null : "valueRDFValue must not be null";
this.bNode = bNode;
this.keyRDFValue = keyRDFValue;
this.valueRDFValue = valueRDFValue;
}
/** Constructs a new MapEntry object.
*
* @param key the map entry key
* @param value the map entry value
*/
MapEntry(final Object key, final Object value) {
//Preconditions
assert key != null : "key must not be null";
assert value != null : "value must not be null";
this.key = key;
this.value = value;
}
/** Makes a map entry given the blank node that persisted it.
*
* @param bNode the blank node that persisted the map entry
* @param repositoryConnection the repository connection
* @param effectiveContextURI the effective context term
* @return the map entry
*/
static MapEntry makeMapEntry(
final BNode bNode,
final RepositoryConnection repositoryConnection,
final URI effectiveContextURI) {
//Preconditions
assert bNode != null : "bNode must not be null";
assert repositoryConnection != null : "repositoryConnection must not be null";
// get the persisted map entry key RDF value
final Value keyRDFValue = RDFUtility.getObjectGivenSubjectAndPredicate(
bNode, // subject
PERSISTENT_MAP_ENTRY_KEY_URI, // predicate
effectiveContextURI, // context
repositoryConnection);
assert keyRDFValue != null : "\nsubject: " + bNode + "\npredicate: " + PERSISTENT_MAP_ENTRY_KEY_URI + "\ncontext: " + effectiveContextURI;
// get the persisted map-entry-value RDF-value
final Value valueRDFValue = RDFUtility.getObjectGivenSubjectAndPredicate(
bNode, // subject
PERSISTENT_MAP_ENTRY_VALUE_URI, // predicate
effectiveContextURI, // context
repositoryConnection);
assert keyRDFValue != null : "\nsubject: " + bNode + "\npredicate: " + PERSISTENT_MAP_ENTRY_VALUE_URI + "\ncontext: " + effectiveContextURI;
return new MapEntry(bNode, keyRDFValue, valueRDFValue);
}
/** Returns a string representation of this object.
*
* @return a string representation of this object
*/
@Override
public String toString() {
return key + "-->" + value;
}
/** Returns whether some other object equals this one.
*
* @param obj the other object
* @return whether some other object equals this one
*/
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final MapEntry other = (MapEntry) obj;
if (this.key != other.key && (this.key == null || !this.key.equals(other.key))) {
return false;
}
if (this.value != other.value && (this.value == null || !this.value.equals(other.value))) {
return false;
}
return true;
}
/** Returns a hash code for this object.
*
* @return a hash code for this object
*/
@Override
public int hashCode() {
int hash = 7;
hash = 37 * hash + Objects.hashCode(this.key);
hash = 37 * hash + Objects.hashCode(this.keyRDFValue);
hash = 37 * hash + Objects.hashCode(this.value);
hash = 37 * hash + Objects.hashCode(this.valueRDFValue);
hash = 37 * hash + Objects.hashCode(this.bNode);
return hash;
}
}
}