RDFEntityPersister.java from Texai at Krugle
Show RDFEntityPersister.java syntax highlighted
/*
* RDFEntityPersister.java
*
* Created on October 31, 2006, 11:23 AM
*
* Description: This class persists domain entities into the RDF store,
* mapping entity associations onto RDF triples.
*
* 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 com.sun.xml.bind.DatatypeConverterImpl;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.UUID;
import javax.xml.bind.DatatypeConverterInterface;
import javax.xml.namespace.QName;
import net.jcip.annotations.NotThreadSafe;
import net.sf.cglib.proxy.Factory;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.apache.log4j.Logger;
import org.openrdf.OpenRDFException;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.datatypes.XMLDatatypeUtil;
import org.openrdf.model.vocabulary.OWL;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.model.vocabulary.RDFS;
import org.openrdf.model.vocabulary.XMLSchema;
import org.openrdf.query.BindingSet;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.TupleQuery;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.texai.kb.Constants;
import org.texai.kb.persistence.lazy.LazyList;
import org.texai.kb.persistence.lazy.LazySet;
import org.texai.util.TexaiException;
/** This class persists new or updated entities to the Sesame RDF store. New RDF classes and predicates are automatically
* defined in the RDF store. The persist() method cascades to new RDF entities that are field values of the entity being persisted.
*
* @author reed
*/
@NotThreadSafe
public class RDFEntityPersister // NOPMD
extends AbstractRDFEntityAccessor {
// session variables
/** the logger */
private final Logger logger = Logger.getLogger(RDFEntityPersister.class); // NOPMD
/** the indicator whether the debug logging level is enabled */
private final boolean isDebugEnabled;
/** the Sesame repository connection */
private final RepositoryConnection repositoryConnection;
/** the Sesame value factory */
private final ValueFactory valueFactory;
/** the RDF utility bean */
private final RDFUtility rdfUtility;
/** the query that gathers objects for a given subject and predicate. */
private final TupleQuery objectsTupleQuery;
/** the query that gathers existing types for a given subject. */
private final TupleQuery existingTypesTupleQuery;
/** the query that gathers existing subClassOfs for a given entity class. */
private final TupleQuery existingSubClassOfsTupleQuery;
/** the query that gathers objects for a given predicate and object. */
private final TupleQuery subjectsTupleQuery;
/** the set of defined class and predicate URIs */
private final Set<URI> definedClassAndPredicateURIs;
/** the XML datatype converter */
private final DatatypeConverterInterface datatypeConverter;
/** the URI http://sw.cyc.com/2006/07/27/cyc/Collection */
private final URI uriCollection;
/** the URI http://sw.cyc.com/2006/07/27/cyc/conceptuallyRelated */
private final URI uriConceptuallyRelated;
/** the URI http://texai.org/texai/domainEntityClassName */
private final URI uriDomainEntityClassName;
/** the URI http://sw.cyc.com/2006/07/27/cyc/FirstOrderCollection */
private final URI uriFirstOrderCollection;
/** the URI http://sw.cyc.com/2006/07/27/cyc/UniversalVocabularyMt */
private final URI uriUniversalVocabularyMt;
// variables used to persist a particular entity
/** the indicator that the entity instance is new */
private boolean isNewDomainInstance;
/** the stack of RDF entity information that allows the session bean to perform recursive method calls */
private Stack<RDFEntityInfo> rdfEntityInfoStack;
/** the indicator to validate persisted statements */
private boolean areStatementsValidated = false;
/** the context for recursive calls to the persist method. */
private URI recursiveContextURI;
/** Creates a new instance of RDFEntityPersister.
*
* @param repositoryConnection the Sesame repository connection
* @param valueFactory the Sesame value factory
*/
public RDFEntityPersister(
final RepositoryConnection repositoryConnection,
final ValueFactory valueFactory) {
//preconditions
assert repositoryConnection != null : "repositoryConnection must not be null";
assert valueFactory != null : "valueFactory must not be null";
this.repositoryConnection = repositoryConnection;
this.valueFactory = valueFactory;
rdfUtility = new RDFUtility(repositoryConnection, valueFactory);
try {
objectsTupleQuery = repositoryConnection.prepareTupleQuery(
QueryLanguage.SERQL,
"SELECT o, c FROM CONTEXT c {s} p {o}");
existingTypesTupleQuery = repositoryConnection.prepareTupleQuery(
QueryLanguage.SERQL,
"SELECT typeTerm FROM {s} rdf:type {typeTerm}");
existingSubClassOfsTupleQuery = repositoryConnection.prepareTupleQuery(
QueryLanguage.SERQL,
"SELECT superClassTerm FROM {classTerm} rdfs:subClassOf {superClassTerm}");
subjectsTupleQuery = repositoryConnection.prepareTupleQuery(
QueryLanguage.SERQL,
"SELECT s, c FROM CONTEXT c {s} p {o}");
} catch (final MalformedQueryException ex) {
throw new TexaiException(ex);
} catch (final RepositoryException ex) {
throw new TexaiException(ex);
}
definedClassAndPredicateURIs = new HashSet<URI>();
rdfEntityInfoStack = new Stack<RDFEntityInfo>();
datatypeConverter = DatatypeConverterImpl.theInstance;
uriCollection = valueFactory.createURI(Constants.TERM_COLLECTION);
uriConceptuallyRelated = valueFactory.createURI(Constants.TERM_CONCEPTUALLY_RELATED);
uriDomainEntityClassName = valueFactory.createURI(Constants.TERM_DOMAIN_ENTITY_CLASS_NAME);
uriFirstOrderCollection = valueFactory.createURI(Constants.TERM_FIRST_ORDER_COLLECTION);
uriUniversalVocabularyMt = valueFactory.createURI(Constants.TERM_UNIVERSAL_VOCABULARY_MT);
isDebugEnabled = logger.isDebugEnabled();
}
/** Persists the given RDF entity as propositions in the RDF store.
*
* @param rdfEntity the RDF entity
* @return the instance URI that represents the RDF entity
*/
public URI persist(final Object rdfEntity) {
//Preconditions
assert rdfEntity != null : "rdfEntity must not be null";
assert isRDFEntity(rdfEntity) : rdfEntity + "(" + rdfEntity.getClass().getName()
+ ") must be have a @RDFEntity class level annotation in " + Arrays.toString(rdfEntity.getClass().getAnnotations());
final boolean wasStackEmpty = rdfEntityInfoStack.empty();
initializeAbstractSessionState();
initializeSessionState();
if (isDebugEnabled) {
logger.debug(stackLevel() + "persisting " + rdfEntity);
}
setRDFEntity(rdfEntity);
setRDFEntityClass(rdfEntity.getClass());
gatherAnnotationsForRDFEntityClass();
configureRDFEntitySettings();
if (!definedClassAndPredicateURIs.contains(getClassURI())) {
persistTypes();
persistSubClassOfs();
definedClassAndPredicateURIs.add(getClassURI());
}
findOrCreateDomainInstanceURI();
persistFields();
//Postconditions
assert getInstanceURI() != null : "instanceURI must not be null";
assert wasStackEmpty == rdfEntityInfoStack.empty() : "beginning stack empty status " + wasStackEmpty
+ " must equal ending stack status " + rdfEntityInfoStack.empty();
return getInstanceURI();
}
/** Gets the indicator to validate persisted statements.
*
* @return the indicator whether to validate persisted statements
*/
public boolean getAreStatementsValidated() {
return areStatementsValidated;
}
/** Sets the indicator to validate persisted statements.
*
* @param areStatementsValidated the indicator whether to validate persisted statements
*/
public void setAreStatementsValidated(boolean areStatementsValidated) {
this.areStatementsValidated = areStatementsValidated;
}
/** Gets the logger.
*
* @return the logger
*/
protected Logger getLogger() {
return logger;
}
/** Gets the value factory.
*
* @return the value factory
*/
protected ValueFactory getValueFactory() {
return valueFactory;
}
/** Gathers the existing type classes for which the entity class is a direct instance.
*
* @return the existing type classes names for which the entity class is a direct instance
*/
private Set<URI> gatherExistingTypes() {
//Preconditions
assert getClassURI() != null : "classURI must not be null";
final Set<URI> existingTypeURIs = new HashSet<URI>();
try {
existingTypesTupleQuery.setBinding("s", getClassURI());
final TupleQueryResult tupleQueryResult = existingTypesTupleQuery.evaluate();
while (tupleQueryResult.hasNext()) {
existingTypeURIs.add((URI) tupleQueryResult.next().getBinding("typeTerm").getValue()); // NOPMD
}
tupleQueryResult.close();
} catch (final OpenRDFException ex) {
throw new TexaiException(ex);
}
return existingTypeURIs;
}
/** Gathers the existing classes for which the entity class is a direct subclass.
*
* @return the existing classes for which the entity class is a direct subclass
*/
private Set<URI> gatherExistingSubClassOfs() {
//Preconditions
assert getClassURI() != null : "classURI must not be null";
final Set<URI> existingSubClassOfURIs = new HashSet<URI>();
try {
existingSubClassOfsTupleQuery.setBinding("classTerm", getClassURI());
final TupleQueryResult tupleQueryResult = existingSubClassOfsTupleQuery.evaluate();
while (tupleQueryResult.hasNext()) {
existingSubClassOfURIs.add((URI) tupleQueryResult.next().getBinding("superClassTerm").getValue());
}
tupleQueryResult.close();
} catch (final OpenRDFException ex) {
throw new TexaiException(ex);
}
return existingSubClassOfURIs;
}
/**
* Persists the RDF entity class typeURI statements.
*/
private void persistTypes() {
//Preconditions
assert getClassURI() != null : "classURI must not be null";
final Set<URI> existingTypeURIs = gatherExistingTypes();
if (existingTypeURIs.isEmpty() && getTypeURIs().length == 0) {
// default to FirstOrderCollection for a new domain class
final URI[] typeURIs = {uriFirstOrderCollection};
setTypeURIs(typeURIs);
}
try {
for (final URI typeURI : getTypeURIs()) {
if (!existingTypeURIs.contains(typeURI)) {
if (isDebugEnabled) {
logger.debug(stackLevel() + "adding type: " + typeURI);
}
final Statement statement = valueFactory.createStatement(
getClassURI(),
RDF.TYPE,
typeURI,
uriUniversalVocabularyMt);
repositoryConnection.add(statement);
logger.info("persisted: " + rdfUtility.formatStatement(statement)); // NOPMD
}
}
if (getTypeURIs().length > 0) {
// if the RDFEntity annotation specified typeURI, then delete any existing typeURI relationships not contained in the specification
final List<URI> typeURIsList = Arrays.asList(getTypeURIs());
for (final URI existingTypeURI : existingTypeURIs) {
if (!typeURIsList.contains(existingTypeURI)) {
if (isDebugEnabled) {
logger.debug(stackLevel() + "deleting existingType: " + existingTypeURI);
}
final Statement statement = valueFactory.createStatement(
getClassURI(),
RDF.TYPE,
existingTypeURI,
uriUniversalVocabularyMt);
repositoryConnection.remove(statement);
logger.info("removed: " + rdfUtility.formatStatement(statement)); // NOPMD
}
}
}
} catch (final RepositoryException ex) {
throw new TexaiException(ex);
}
}
/** Persists the RDF entity class genls propositions. */
private void persistSubClassOfs() {
//Preconditions
assert getClassURI() != null : "classURI must not be null";
final Set<URI> existingSubClassOfURIs = gatherExistingSubClassOfs();
if (existingSubClassOfURIs.isEmpty() && getSubClassOfURIs().length == 0) {
// default to Collection for a new domain class
final URI[] subClassOfURIs = {uriCollection};
setSubClassOfURIs(subClassOfURIs);
}
try {
for (final URI subClassOfURI : getSubClassOfURIs()) {
if (!existingSubClassOfURIs.contains(subClassOfURI)) {
if (isDebugEnabled) {
logger.debug(stackLevel() + "adding subClassOf: " + subClassOfURI);
}
final Statement statement = valueFactory.createStatement(
getClassURI(),
RDFS.SUBCLASSOF,
subClassOfURI,
uriUniversalVocabularyMt);
repositoryConnection.add(statement);
logger.info("persisted: " + rdfUtility.formatStatement(statement)); // NOPMD
}
}
if (getSubClassOfURIs().length > 0) {
// if the RDFEntity annotation specified subClassOf, then delete any existing subClassOf relationships not contained in the specification
final List<URI> subClassOfURIsList = Arrays.asList(getSubClassOfURIs());
for (final URI existingSubClassOfURI : existingSubClassOfURIs) {
if (!subClassOfURIsList.contains(existingSubClassOfURI)) {
if (isDebugEnabled) {
logger.debug(stackLevel() + "deleting : existingSubClassOf" + existingSubClassOfURI);
}
final Statement statement = valueFactory.createStatement(
getClassURI(),
RDFS.SUBCLASSOF,
existingSubClassOfURI,
uriUniversalVocabularyMt);
repositoryConnection.remove(statement);
logger.info("removed: " + rdfUtility.formatStatement(statement)); // NOPMD
}
}
}
} catch (final RepositoryException ex) {
throw new TexaiException(ex);
}
}
/** Finds or creates the domain instance URI. */
private void findOrCreateDomainInstanceURI() {
//Preconditions
assert getFieldAnnotationDictionary() != null : "fieldAnnotationDictionary must not be null";
assert !getFieldAnnotationDictionary().isEmpty() : "fieldAnnotationDictionary must not be empty";
assert getClassURI() != null : "classURI must not be null";
assert getRDFEntity() != null : "rdfEntity() must not be null";
final Field idField = getIdField();
if (idField == null) {
throw new TexaiException("Id field not found for RDF entity " + getRDFEntity());
}
if (!idField.isAccessible()) {
idField.setAccessible(true);
}
Object value;
try {
value = idField.get(getRDFEntity());
} catch (final IllegalArgumentException ex) {
throw new TexaiException(ex);
} catch (final IllegalAccessException ex) {
throw new TexaiException(ex);
}
if (value == null) {
setInstanceURI(makeURI(getClassURI() + "_" + UUID.randomUUID().toString()));
try {
if (idField.getType().equals(String.class)) {
idField.set(getRDFEntity(), getInstanceURI().toString());
} else if (URI.class.isAssignableFrom(idField.getType())) {
idField.set(getRDFEntity(), getInstanceURI());
} else if (idField.getType().equals(java.net.URI.class)) {
try {
idField.set(getRDFEntity(), new java.net.URI(getInstanceURI().toString()));
} catch (final IllegalArgumentException ex) {
throw new TexaiException(ex);
} catch (final URISyntaxException ex) {
throw new TexaiException(ex);
} catch (final IllegalAccessException ex) {
throw new TexaiException(ex);
}
} else {
throw new TexaiException("Id field is not a supported type");
}
} catch (final IllegalArgumentException ex) {
throw new TexaiException(ex);
} catch (final IllegalAccessException ex) {
throw new TexaiException(ex);
}
if (isDebugEnabled) {
logger.debug(stackLevel() + " created new instance " + getInstanceURI());
}
try {
final Statement typeStatement = valueFactory.createStatement(
getInstanceURI(),
RDF.TYPE,
getClassURI(),
uriUniversalVocabularyMt);
repositoryConnection.add(typeStatement);
logger.info("persisted: " + rdfUtility.formatStatement(typeStatement));
assert getRDFEntityClass() != null : "rdfEntityClass must not be null";
final Statement classNameStatement = valueFactory.createStatement(
getInstanceURI(),
uriDomainEntityClassName,
valueFactory.createLiteral(getRDFEntityClass().getName()),
uriUniversalVocabularyMt);
repositoryConnection.add(classNameStatement);
logger.info("persisted: " + rdfUtility.formatStatement(classNameStatement));
} catch (final RepositoryException ex) {
throw new TexaiException(ex);
}
} else {
if (URI.class.isAssignableFrom(value.getClass())) {
setInstanceURI((URI) value);
} else {
setInstanceURI(makeURI(value.toString()));
}
isNewDomainInstance = false;
if (isDebugEnabled) {
logger.debug(stackLevel() + " Id specifies existing instance " + getInstanceURI());
}
}
final Cache cache = CacheManager.getInstance().getCache(Constants.CACHE_CONNECTED_RDF_ENTITIES);
assert cache != null : "cache not found for: " + Constants.CACHE_CONNECTED_RDF_ENTITIES;
final Element element = new Element(getRDFEntity(), getInstanceURI());
cache.put(element);
//Postconditions
assert getInstanceURI() != null : "instanceURI must not be null";
}
/** Iterates over the fields in the RDF entity instance and persists them
* to the RDF store as propositions.
*/
private void persistFields() {
for (final Field field : getFieldAnnotationDictionary().keySet()) {
final Annotation annotation = getFieldAnnotationDictionary().get(field);
if (isDebugEnabled) {
logger.debug(stackLevel() + "field: " + field + ", annotation: " + annotation);
}
if ("@javax.persistence.Id()".equals(annotation.toString())) {
if (isDebugEnabled) {
logger.debug(stackLevel() + " skipping Id field");
}
continue;
} else {
if (annotation instanceof RDFProperty) {
persistField(field, (RDFProperty) annotation);
}
}
}
}
//TODO persist list and array fields as rdf Collections, drop @OrderBy
// <instance> <predicate> <bnode_1>
// <bnode_1> rdf:first <element1>
// <bnode_1> rdf:rest <bnode_2>
// <bnode_2> rdf:first <element2>
// <bnode_2> rdf:rest <bnode_3>
// ...
// <bnode_N> rdf:first <elementN>
// <bnode_N> rdf:rest rdfs:nil
/** Persists the given field according to the given rdf property.
*
* @param field the given RDF entity instance field
* @param rdfProperty the property annotation associated with the field
*/
@SuppressWarnings({"unchecked"})
private void persistField(final Field field, final RDFProperty rdfProperty) { // NOPMD
//Preconditions
assert field != null : "field must not be null"; // NOPMD
assert rdfProperty != null : "rdfProperty must not be null";
if (isDebugEnabled) {
logger.debug(stackLevel() + " processing rdf property: " + rdfProperty);
}
// obtain the value object
if (!field.isAccessible()) {
field.setAccessible(true);
}
Object value;
try {
value = field.get(getRDFEntity());
} catch (final IllegalArgumentException ex) {
throw new TexaiException(ex);
} catch (final IllegalAccessException ex) {
throw new TexaiException(ex);
}
final Class fieldType = field.getType();
if (isDebugEnabled) {
logger.debug(stackLevel() + " field type: " + fieldType.getName());
}
if (value != null) {
if (value instanceof LazySet || value instanceof LazyList) {
if (isDebugEnabled) {
logger.debug(stackLevel() + " skipping lazyily loaded field which has not yet been loaded");
}
return; // NOPMD
}
if (Factory.class.isAssignableFrom(value.getClass())) {
if (isDebugEnabled) {
logger.debug(stackLevel() + " skipping lazyily loaded proxy field which has not yet been loaded");
}
return; // NOPMD
}
}
final boolean isBooleanField = "boolean".equals(fieldType.getName());
// determine the predicate
final URI predicate = findPredicate(field, rdfProperty, isBooleanField);
// obtain the existings
List<Value> existingRDFValues;
if (isNewDomainInstance) {
existingRDFValues = Collections.EMPTY_LIST;
} else if (rdfProperty.inverse()) {
try {
subjectsTupleQuery.setBinding("p", predicate);
subjectsTupleQuery.setBinding("o", getInstanceURI());
subjectsTupleQuery.setBinding("c", getContextURI());
final TupleQueryResult tupleQueryResult = subjectsTupleQuery.evaluate();
existingRDFValues = new ArrayList<Value>();
while (tupleQueryResult.hasNext()) {
existingRDFValues.add(tupleQueryResult.next().getBinding("s").getValue());
}
tupleQueryResult.close();
} catch (final OpenRDFException ex) {
throw new TexaiException(ex);
}
} else {
try {
objectsTupleQuery.setBinding("s", getInstanceURI());
objectsTupleQuery.setBinding("p", predicate);
objectsTupleQuery.setBinding("c", getContextURI());
final TupleQueryResult tupleQueryResult = objectsTupleQuery.evaluate();
existingRDFValues = new ArrayList<Value>();
while (tupleQueryResult.hasNext()) {
existingRDFValues.add(tupleQueryResult.next().getBinding("o").getValue());
}
tupleQueryResult.close();
} catch (final OpenRDFException ex) {
throw new TexaiException(ex);
}
}
Set<Value> assertedValueTerms = null;
if (isDebugEnabled) {
logger.debug(stackLevel() + " existing RDF values for predicate " + predicate + ": " + existingRDFValues);
}
// persist by the field type
if (isBooleanField) {
final String trueClassName = rdfProperty.trueClass();
if (trueClassName.isEmpty()) {
throw new TexaiException("trueClass must be specified for boolean field " + field.getName());
}
final String falseClassName = rdfProperty.falseClass();
if (falseClassName.isEmpty()) {
throw new TexaiException("falseClass must be specified for boolean field " + field.getName());
}
persistBooleanField(
(Boolean) value,
existingRDFValues,
predicate,
makeURI(trueClassName),
makeURI(falseClassName),
field);
return;
}
// persist the current value(s)
if (value == null) {
if (isDebugEnabled) {
logger.debug(stackLevel() + " field value is null: " + field);
}
} else if (value instanceof Collection) {
if (isDebugEnabled) {
logger.debug(stackLevel() + " field value is Collection: " + field + " value: " + value);
}
assertedValueTerms = persistFieldValues(
(Collection) value,
existingRDFValues,
rdfProperty,
predicate);
} else if (field.getType().isArray()) {
if (isDebugEnabled) {
logger.debug(stackLevel() + " field value is Array: " + field);
}
final int arrayLength = Array.getLength(value);
final List<Object> valueObjects = new ArrayList<Object>(arrayLength);
for (int i = 0; i < arrayLength; i++) {
valueObjects.add(Array.get(value, i));
}
assertedValueTerms = persistFieldValues(
valueObjects,
existingRDFValues,
rdfProperty,
predicate);
} else {
if (isDebugEnabled) {
logger.debug(stackLevel() + " field value is Object: " + field);
}
final List<Object> valueList = new ArrayList<Object>(1);
valueList.add(value);
assertedValueTerms = persistFieldValues(
valueList,
existingRDFValues,
rdfProperty,
predicate);
}
// delete previous associations if no longer applicable
for (final Value existingRDFValue : existingRDFValues) {
// weaker form of the membership test to determine equal RDF literals
boolean isObsoleteValue = true;
final String existingRDFValueString = existingRDFValue.toString();
for (final Value assertedValueTerm : assertedValueTerms) {
if (assertedValueTerm.toString().equals(existingRDFValueString)) {
isObsoleteValue = false;
break;
}
}
if (isObsoleteValue) {
if (isDebugEnabled) {
logger.debug(stackLevel() + "deleting obsolete value: " + existingRDFValue + " for predicate: " + predicate);
}
final Statement statement = valueFactory.createStatement(
getInstanceURI(),
predicate,
existingRDFValue,
getContextURI());
try {
repositoryConnection.remove(statement);
} catch (final RepositoryException ex) {
throw new TexaiException(ex);
}
logger.info("deleted: " + rdfUtility.formatStatement(statement));
}
}
}
/** Finds the predicate that will be used to persist the current field.
*
* @param field the given RDF entity instance field
* @param rdfProperty the property annotation associated with the field
* @param isBooleanField the indicator that the current field is a boolean type
* @return the predicate that will be used to persist the current field
*/
private URI findPredicate(final Field field, final RDFProperty rdfProperty, final boolean isBooleanField) {
//Preconditions
assert field != null : "field must not be null";
assert rdfProperty != null : "rdfProperty must not be null";
URI predicate;
if (isBooleanField) {
predicate = RDF.TYPE;
} else if (rdfProperty.predicate().isEmpty()) {
throw new TexaiException("predicate annotation property is missing");
} else {
predicate = makeURI(rdfProperty.predicate());
}
// check whether to define the predicate
if (!definedClassAndPredicateURIs.contains(predicate)) {
final Set<URI> existingTypes = new HashSet<URI>();
existingTypesTupleQuery.setBinding("s", predicate);
TupleQueryResult tupleQueryResult;
try {
tupleQueryResult = existingTypesTupleQuery.evaluate();
while (tupleQueryResult.hasNext()) {
existingTypes.add((URI) tupleQueryResult.next().getBinding("typeTerm").getValue());
}
tupleQueryResult.close();
} catch (final QueryEvaluationException ex) {
throw new TexaiException(ex);
}
if (existingTypes.isEmpty()
&& (rdfProperty.subPropertyOf().length > 0
|| !rdfProperty.domain().isEmpty()
|| !rdfProperty.range().isEmpty())) {
defineNewPredicate(
rdfProperty,
predicate,
field);
}
definedClassAndPredicateURIs.add(predicate);
}
//Postconditions
assert predicate != null : "predicate must not be null";
return predicate;
}
/** Persists the given boolean field value. When the boolean value is true, a statement of the form
* <instance-term> <rdf:type> <true-class> is asserted in the RDF store, otherwise a statement of the form
* <instance-term> <rdf:type> <false-class> is asserted.
*
* @param value the boolean value
* @param existingValues the existing values
* @param predicate the predicate that represents the association
* @param trueClass the class of RDF instances for which the boolean association holds true
* @param falseClass the class of RDF instances for which the boolean association holds false
* @param field the given RDF entity instance field
*/
@SuppressWarnings("unchecked")
private void persistBooleanField(
final Boolean value,
final List<Value> existingValues,
final URI predicate,
final URI trueClass,
final URI falseClass,
final Field field) {
//Preconditions
assert value != null : "value must not be null";
assert existingValues != null : "existingValues must not be null";
assert predicate != null : "predicate must not be null";
assert trueClass != null : "trueClassName must not be null";
assert falseClass != null : "falseClassName must not be null";
try {
boolean haveCreatedTrueOrFalseClassTerm = false;
// check whether to define the true and false classes
if (!definedClassAndPredicateURIs.contains(getClassURI())) {
// query for existing definition of the true class
final Set<URI> existingTypes = new HashSet<URI>();
existingTypesTupleQuery.setBinding("s", trueClass);
TupleQueryResult tupleQueryResult = existingTypesTupleQuery.evaluate();
while (tupleQueryResult.hasNext()) {
existingTypes.add((URI) tupleQueryResult.next().getBinding("typeTerm").getValue());
}
tupleQueryResult.close();
if (existingTypes.isEmpty()) {
// define true class
final List<URI> types = new ArrayList<URI>();
types.add(uriFirstOrderCollection);
final List<URI> subClassOfURIs = new ArrayList<URI>();
subClassOfURIs.add(getClassURI());
rdfUtility.defineRDFClass(
trueClass,
"This is the collection of " + getClassURI() + " instances for which boolean property " + field.getName() + " holds true.",
types,
subClassOfURIs);
haveCreatedTrueOrFalseClassTerm = true;
}
// query for existing definition of the false class
existingTypes.clear();
existingTypesTupleQuery.setBinding("s", falseClass);
tupleQueryResult = existingTypesTupleQuery.evaluate();
while (tupleQueryResult.hasNext()) {
existingTypes.add((URI) tupleQueryResult.next().getBinding("typeTerm").getValue());
}
tupleQueryResult.close();
if (existingTypes.isEmpty()) {
// define false class
final List<URI> types = new ArrayList<URI>();
types.add(uriFirstOrderCollection);
final List<URI> subClassOfURIs = new ArrayList<URI>();
subClassOfURIs.add(getClassURI());
rdfUtility.defineRDFClass(
falseClass,
"This is the collection of " + getClassURI() + " instances for which boolean property " + field.getName() + " holds false.",
types,
subClassOfURIs);
haveCreatedTrueOrFalseClassTerm = true;
}
}
if (haveCreatedTrueOrFalseClassTerm) {
rdfUtility.assertDisjoint(trueClass, falseClass);
}
// assert the truth value relationship
if (value.booleanValue()) {
if (!existingValues.contains(trueClass)) {
final Statement statement = valueFactory.createStatement(
getInstanceURI(),
RDF.TYPE,
trueClass,
getContextURI());
repositoryConnection.add(statement);
logger.info("persisted: " + rdfUtility.formatStatement(statement));
}
if (existingValues.contains(falseClass)) {
if (isDebugEnabled) {
logger.debug(stackLevel() + "deleting existing value URI: " + falseClass + " for predicate: " + predicate);
}
final Statement statement = valueFactory.createStatement(
getInstanceURI(),
RDF.TYPE,
falseClass,
getContextURI());
repositoryConnection.remove(statement);
logger.info("deleted: " + rdfUtility.formatStatement(statement));
}
} else {
if (!existingValues.contains(falseClass)) {
final Statement statement = valueFactory.createStatement(
getInstanceURI(),
RDF.TYPE,
falseClass,
getContextURI());
repositoryConnection.add(statement);
logger.info("persisted: " + rdfUtility.formatStatement(statement));
}
if (existingValues.contains(trueClass)) {
if (isDebugEnabled) {
logger.debug(stackLevel() + "deleting existing value URI: " + trueClass + " for predicate: " + predicate);
}
final Statement statement = valueFactory.createStatement(
getInstanceURI(),
RDF.TYPE,
trueClass,
getContextURI());
repositoryConnection.remove(statement);
logger.info("deleted: " + rdfUtility.formatStatement(statement));
}
}
} catch (final OpenRDFException ex) {
throw new TexaiException(ex);
}
}
/**
* Persists the field value(s) and strength(s).
*
*
* @param valueList the list of java values
* @param existingRDFValues the existing RDF values
* @param isInverseProperty the indicator that the property is to be inverted with respect to treatment of subject and object
* @param predicateURI the predicate that represents the association
* @return the set of asserted RDF values
*/
@SuppressWarnings("unchecked")
private Set<Value> persistFieldValues(
final Collection valueList,
final List<Value> existingRDFValues,
final RDFProperty rdfProperty,
final URI predicateURI) {
//Preconditions
assert valueList != null : "valueList must not be null";
assert existingRDFValues != null : "existingValues must not be null";
assert getContextURI() != null : "contextURI must not be null";
final Set<Value> assertedRDFValues = new HashSet<Value>();
final List<Value> rdfValues = createRDFValueTerms(valueList, rdfProperty);
final Iterator<Value> rdfValues_iter = rdfValues.iterator();
while (rdfValues_iter.hasNext()) {
final Value rdfValue = rdfValues_iter.next();
// weaker form of the membership test to determine equal RDF literals
boolean isNewValue = true;
final String rdfValueString = rdfValue.toString();
for (final Value existingRDFValue : existingRDFValues) {
if (existingRDFValue.toString().equals(rdfValueString)) {
isNewValue = false;
break;
}
}
if (isNewValue) {
Statement statement;
if (rdfProperty.inverse()) {
if (!(rdfValue instanceof URI)) {
throw new TexaiException(rdfValue + " must be a URI for inverse property " + predicateURI);
}
statement = valueFactory.createStatement(
(URI) rdfValue,
predicateURI,
getInstanceURI(),
getContextURI());
} else {
statement = valueFactory.createStatement(
getInstanceURI(),
predicateURI,
rdfValue,
getContextURI());
}
try {
repositoryConnection.add(statement);
} catch (final RepositoryException ex) {
throw new TexaiException(ex);
}
logger.info("persisted: " + rdfUtility.formatStatement(statement));
}
assertedRDFValues.add(rdfValue);
}
return assertedRDFValues;
}
/** Creates a new predicate for the given rdf property.
*
* @param rdfProperty the given rdf property
* @param predicate the predicate
* @param field the given field
* @return a new predicate for the given rdf property
*/
private void defineNewPredicate(
final RDFProperty rdfProperty,
final URI predicate,
final Field field) {
//Preconditions
assert rdfProperty != null : "rdfProperty must not be null";
assert predicate != null : "predicate must not be null";
assert field != null : "field must not be null";
URI domainURI;
final String domainName = rdfProperty.domain();
if (domainName.length() == 0) {
domainURI = getClassURI();
} else {
domainURI = makeURI(domainName);
}
URI rangeURI;
final String rangeName = rdfProperty.range();
if (rangeName.length() == 0) {
rangeURI = getDefaultTypeURIForFieldType(field);
} else {
rangeURI = makeURI(rangeName);
}
final List<URI> typeURIs = new ArrayList<URI>(1);
if (XMLDatatypeUtil.isBuiltInDatatype(rangeURI)) {
typeURIs.add(OWL.DATATYPEPROPERTY);
} else {
typeURIs.add(OWL.OBJECTPROPERTY);
}
final List<URI> subPropertyOfURIs = new ArrayList<URI>(1);
subPropertyOfURIs.add(uriConceptuallyRelated);
rdfUtility.defineRDFPredicate(
predicate,
(String) null,
typeURIs,
subPropertyOfURIs,
domainURI,
rangeURI);
}
/** For the purpose of defineing a new predicate, returns the RDF type that corresponds to the type of the given field.
*
* @param field the given field
* @return the URI type that corresponds to the type of the given field
*/
private URI getDefaultTypeURIForFieldType(final Field field) {
//Preconditions
assert field != null : "field must not be null";
return getTypeURIForClass(field.getType());
}
/** For the purpose of defineing a new predicate, returns the RDF type that corresponds to the type of the given field.
*
* @param field the given field
* @return the URI type that corresponds to the type of the given field
*/
private URI getTypeURIForClass(final Class fieldClass) {
//Preconditions
assert fieldClass != null : "clazz must not be null";
URI fieldClassURI;
if (fieldClass.equals(Byte.class) || fieldClass.equals(byte.class)) {
fieldClassURI = XMLSchema.BYTE;
} else if (fieldClass.equals(Short.class) || fieldClass.equals(short.class)) {
fieldClassURI = XMLSchema.SHORT;
} else if (fieldClass.equals(Integer.class) || fieldClass.equals(int.class)) {
fieldClassURI = XMLSchema.INT;
} else if (fieldClass.equals(Long.class) || fieldClass.equals(long.class)) {
fieldClassURI = XMLSchema.LONG;
} else if (fieldClass.equals(Float.class) || fieldClass.equals(float.class)) {
fieldClassURI = XMLSchema.FLOAT;
} else if (fieldClass.equals(Double.class) || fieldClass.equals(double.class)) {
fieldClassURI = XMLSchema.DOUBLE;
} else if (fieldClass.equals(String.class)) {
fieldClassURI = XMLSchema.STRING;
} else if (fieldClass.equals(BigInteger.class)) {
fieldClassURI = XMLSchema.INTEGER;
} else if (fieldClass.equals(BigDecimal.class)) {
fieldClassURI = XMLSchema.DECIMAL;
} else if (fieldClass.equals(Calendar.class)) {
fieldClassURI = XMLSchema.DATETIME;
} else if (fieldClass.equals(Date.class)) {
fieldClassURI = XMLSchema.DATETIME;
} else if (fieldClass.equals(QName.class)) {
fieldClassURI = XMLSchema.QNAME;
} else if (fieldClass.equals(java.net.URI.class)) {
fieldClassURI = XMLSchema.ANYURI;
} else if (fieldClass.equals(byte[].class)) {
fieldClassURI = XMLSchema.BASE64BINARY;
} else if (fieldClass.isArray()) {
fieldClassURI = getTypeURIForClass(fieldClass.getComponentType());
} else {
fieldClassURI = getClassURI(fieldClass);
}
//Postconditions
assert fieldClassURI != null : "classURI must not be null";
return fieldClassURI;
}
/** Creates the RDF values for the given value list.
*
* @param valueList the given java value list
* @param rdfProperty the RDF property annotation
* @return the RDF values for the given value list
*/
private List<Value> createRDFValueTerms(final Collection valueList, final RDFProperty rdfProperty) {
//Preconditions
assert valueList != null : "valueList must not be null";
assert rdfProperty != null : "rdfProperty must not be null";
if (isDebugEnabled) {
logger.debug(stackLevel() + " valueList: " + valueList);
}
final List<Value> rdfValues = new ArrayList<Value> (valueList.size());
for (final Object value : valueList) {
rdfValues.add(getRDFValueFromJavaValue(value, rdfProperty));
}
return rdfValues;
}
/** Gets the RDF value that will be persisted for the given java value.
*
* @param value the given java value
* @param rdfProperty the RDF property annotation
* @return the RDF value term for the given value
*/
private Value getRDFValueFromJavaValue(final Object value, final RDFProperty rdfProperty) { // NOPMD
//Preconditions
assert value != null : "value must not be null";
assert rdfProperty != null : "rdfProperty must not be null";
Value rdfValue = null;
if (value instanceof Byte) {
if (rdfProperty.range().endsWith("unsignedByte")) {
if (((Byte) value).byteValue() < 0) {
throw new TexaiException("attempt to persist a negative byte value [" + value + "] for an unsignedByte field");
}
rdfValue = valueFactory.createLiteral(value.toString(), XMLSchema.UNSIGNED_BYTE);
} else {
rdfValue = valueFactory.createLiteral((Byte) value);
}
} else if (value instanceof Short) {
if (rdfProperty.range().endsWith("unsignedShort")) {
if (((Short) value).shortValue() < 0) {
throw new TexaiException("attempt to persist a negative short value [" + value + "] for an unsignedShort field");
}
rdfValue = valueFactory.createLiteral(value.toString(), XMLSchema.UNSIGNED_SHORT);
} else {
rdfValue = valueFactory.createLiteral((Short) value);
}
} else if (value instanceof Integer) {
if (rdfProperty.range().isEmpty()) {
rdfValue = valueFactory.createLiteral((Integer) value);
} else if (rdfProperty.range().endsWith("unsignedInt")) {
if (((Integer) value).intValue() < 0) {
throw new TexaiException("attempt to persist a negative integer value [" + value + "] for an unsignedInt field");
}
rdfValue = valueFactory.createLiteral(value.toString(), XMLSchema.UNSIGNED_INT);
} else {
rdfValue = valueFactory.createLiteral((Integer) value);
}
} else if (value instanceof Long) {
if (rdfProperty.range().endsWith("unsignedLong")) {
if (((Long) value).longValue() < 0) {
throw new TexaiException("attempt to persist a negative long value [" + value + "] for an unsignedLong field");
}
rdfValue = valueFactory.createLiteral(value.toString(), XMLSchema.UNSIGNED_LONG);
} else {
rdfValue = valueFactory.createLiteral((Long) value);
}
} else if (value instanceof Float) {
rdfValue = valueFactory.createLiteral((Float) value);
} else if (value instanceof Double) {
rdfValue = valueFactory.createLiteral((Double) value);
} else if (value instanceof String) {
rdfValue = valueFactory.createLiteral((String) value);
} else if (value instanceof BigInteger) {
if (rdfProperty.range().endsWith("positiveInteger")) {
if (((BigInteger) value).longValue() <= 0) {
throw new TexaiException("attempt to persist a non-positive integer value [" + value + "] for an positiveInteger field");
}
rdfValue = valueFactory.createLiteral(datatypeConverter.printInteger((BigInteger) value), XMLSchema.POSITIVE_INTEGER);
} else if (rdfProperty.range().endsWith("nonNegativeInteger")) {
if (((BigInteger) value).longValue() < 0) {
throw new TexaiException("attempt to persist a negative integer value [" + value + "] for an nonNegativeInteger field");
}
rdfValue = valueFactory.createLiteral(datatypeConverter.printInteger((BigInteger) value), XMLSchema.NON_NEGATIVE_INTEGER);
} else if (rdfProperty.range().endsWith("nonPositiveInteger")) {
if (((BigInteger) value).longValue() > 0) {
throw new TexaiException("attempt to persist a positive integer value [" + value + "] for an nonPositiveInteger field");
}
rdfValue = valueFactory.createLiteral(datatypeConverter.printInteger((BigInteger) value), XMLSchema.NON_POSITIVE_INTEGER);
} else if (rdfProperty.range().endsWith("negativeInteger")) {
if (((BigInteger) value).longValue() >= 0) {
throw new TexaiException("attempt to persist a non-negative integer value [" + value + "] for an negativeInteger field");
}
rdfValue = valueFactory.createLiteral(datatypeConverter.printInteger((BigInteger) value), XMLSchema.NEGATIVE_INTEGER);
} else {
rdfValue = valueFactory.createLiteral(datatypeConverter.printInteger((BigInteger) value), XMLSchema.INTEGER);
}
} else if (value instanceof BigDecimal) {
rdfValue = valueFactory.createLiteral(datatypeConverter.printDecimal((BigDecimal) value), XMLSchema.DECIMAL);
} else if (value instanceof Calendar) {
rdfValue = valueFactory.createLiteral(datatypeConverter.printDateTime((Calendar) value), XMLSchema.DATETIME);
} else if (value instanceof Date) {
final Calendar calendar = Calendar.getInstance();
calendar.setTime((Date) value);
rdfValue = valueFactory.createLiteral(datatypeConverter.printDateTime(calendar), XMLSchema.DATETIME);
} else if (value instanceof QName) {
throw new TexaiException("QName type is not implemented for persistence");
} else if (value instanceof java.net.URI) {
rdfValue = valueFactory.createLiteral(((java.net.URI) value).toString(), XMLSchema.ANYURI);
} else if (value instanceof Value) {
rdfValue = (Value) value;
} else if (value instanceof Byte[]) {
throw new TexaiException("Byte[] type is not implemented for persistence");
} else if (isRDFEntity(value)) {
final Cache cache = CacheManager.getInstance().getCache(Constants.CACHE_CONNECTED_RDF_ENTITIES);
final Element element = cache.get(value);
if (element != null) {
rdfValue = (Value) element.getObjectValue();
}
if (rdfValue == null) {
// save this session state before the recursive method call because session beans cannot
// create a new session bean instance
if (isDebugEnabled) {
logger.debug(stackLevel() + " saving session state for rdfEntity: " + getRDFEntity());
}
saveAbstractSessionState();
saveSessionState();
rdfValue = persist(value);
restoreAbstractSessionState();
restoreSessionState();
if (isDebugEnabled) {
logger.debug(stackLevel() + " restored session state for rdfEntity: " + getRDFEntity());
} if (isDebugEnabled) {
} else {
logger.debug(stackLevel() + " previously persisted rdfValue: " + rdfValue);
}
}
} else {
throw new TexaiException("unhandled value type (" + value.getClass().getName() + ") " + value);
}
//Postconditions
assert rdfValue != null : "rdfValue must not be null";
return rdfValue;
}
/** Pushes the current session bean state onto a stack and then intitializes the session state. */
public void saveSessionState() {
//Preconditions
assert rdfEntityInfoStack != null : "rdfEntityInfoStack must not be null";
if (isDebugEnabled) {
logger.debug(stackLevel() + "saving session state");
}
rdfEntityInfoStack.push(new RDFEntityInfo(recursiveContextURI, isNewDomainInstance));
}
/** Initializes the session bean state during a recursive method call. */
private void initializeSessionState() {
recursiveContextURI = null; // NOPMD
isNewDomainInstance = true; // NOPMD
}
/** Restores the session state following a recursive method call. */
public void restoreSessionState() {
//Preconditions
assert rdfEntityInfoStack != null : "rdfEntityInfoStack must not be null";
final RDFEntityInfo rdfEntityInfo = rdfEntityInfoStack.pop();
recursiveContextURI = rdfEntityInfo.contextURI;
isNewDomainInstance = rdfEntityInfo.isNewDomainInstance;
if (isDebugEnabled) {
logger.debug(stackLevel() + "restored session state");
}
}
/** Contains the session bean state for recursive method calls. */
private static class RDFEntityInfo {
/** the context into which the class-scoped associations are persisted */
private final URI contextURI; // NOPMD
/** the indicator that the domain instance is new */
private boolean isNewDomainInstance; // NOPMD
/**
* Creates a new RDFEntityInfo instance.
*
* @param contextURI the context into which the class-scoped associations are persisted
* @param isNewDomainInstance the indicator that the domain instance is new
*/
protected RDFEntityInfo(
final URI contextURI,
final boolean isNewDomainInstance) {
this.contextURI = contextURI;
this.isNewDomainInstance = isNewDomainInstance;
}
}
}
See more files for this project here