/* * RDFEntityLoader.java * * Created on October 31, 2006, 11:22 AM * * Description: This class loads RDF entities from the RDF store, * mapping RDF triples onto RDF entity associations. * * 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.lang.annotation.Annotation; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.Stack; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import javax.persistence.FetchType; import javax.xml.bind.DatatypeConverter; import net.jcip.annotations.NotThreadSafe; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.Factory; import org.apache.log4j.Logger; import org.joda.time.DateTime; import org.openrdf.OpenRDFException; import org.openrdf.model.BNode; import org.openrdf.model.Literal; import org.openrdf.model.Statement; import org.openrdf.model.URI; import org.openrdf.model.Value; import org.openrdf.model.impl.URIImpl; import org.openrdf.model.vocabulary.RDF; 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.openrdf.repository.RepositoryResult; import org.texai.kb.Constants; import org.texai.kb.persistence.lazy.LazyList; import org.texai.kb.persistence.lazy.LazyMap; import org.texai.kb.persistence.lazy.LazySet; import org.texai.kb.persistence.lazy.RDFEntityLazyLoader; import org.texai.util.ArraySet; import org.texai.util.StringUtils; import org.texai.util.TexaiException; /** This helper class loads previously persisted entities from the Sesame RDF store. Collection and entity fields are * by default lazily loaded. * * @author reed */ @NotThreadSafe public final class RDFEntityLoader extends AbstractRDFEntityAccessor { // NOPMD /** the cached dictionary of default contexts, class name --> context URI */ private static final Map<String, URI> DEFAULT_CONTEXT_DICTIONARY = new ConcurrentHashMap<>(); /** the URI http://texai.org/texai/overrideContext */ private static final URI URI_OVERRIDE_CONTEXT = new URIImpl(Constants.TERM_OVERRIDE_CONTEXT); /** the logger */ private final Logger logger = Logger.getLogger(RDFEntityLoader.class); // NOPMD /** the indicator whether the debug logging level is enabled */ private final boolean isDebugEnabled; /** the dictionary of connected RDF entities, URI --> RDF instance */ private Map<URI, Object> connectedRDFEntityDictionary = new HashMap<>(); /** the dictionary of class names, class name --> class */ private final Map<String, Class<?>> classNameDictionary = new HashMap<>(); /** the stack of RDF entity information that enables recursive method calls */ private final Stack<RDFEntityInfo> rdfEntityInfoStack = new Stack<>(); /** the predicate values dictionary, predicate --> RDF values */ private Map<URI, List<Value>> predicateValuesDictionary; /** the proxy factory dictionary, field type --> proxy factory */ private final Map<Class<?>, Factory> proxyFactoryDictionary = new HashMap<>(); /** Creates a new instance of RDFEntityLoader. */ public RDFEntityLoader() { super(); isDebugEnabled = logger.isDebugEnabled(); } /** Finds and loads the RDF entity from propositions in the knowledge base given its URI. * * @param repositoryConnection the repository connection * @param instanceURI the URI that represents the RDF entity * @return the RDF entity or null if not found */ public Object find( final RepositoryConnection repositoryConnection, final URI instanceURI) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; // NOPMD assert instanceURI != null : "instanceURI must not be null"; // NOPMD return findRDFEntity( repositoryConnection, null, instanceURI); } /** Finds and loads the RDF entity from propositions in the knowledge base given its URI and class. If the class is an interface * then the class name is looked up from the RDF store before instantiating the entity. * * @param <T> the entity type * @param repositoryConnection the repository connection * @param clazz the RDF entity class * @param instanceURI the URI that represents the RDF entity * @return the RDF entity */ @SuppressWarnings("unchecked") public <T> T find( final RepositoryConnection repositoryConnection, final Class<T> clazz, final URI instanceURI) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; // NOPMD assert clazz != null : "clazz must not be null"; assert instanceURI != null : "instanceURI must not be null"; // NOPMD connectedRDFEntityDictionary.clear(); if (clazz.isInterface()) { return (T) find(repositoryConnection, instanceURI); } else { return findRDFEntity( repositoryConnection, clazz, instanceURI); } } /** Finds and loads the RDF entity from propositions in the knowledge base given its URI, without clearing * the dictionary of connected RDF entities. * * @param <T> the entity type * @param repositoryConnection the repository connection * @param clazz the RDF entity class * @param instanceURI the java.net.URI that represents the RDF entity * @return the RDF entity or null if not found */ public <T> T find( final RepositoryConnection repositoryConnection, final Class<T> clazz, final java.net.URI instanceURI) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; // NOPMD assert clazz != null : "clazz must not be null"; assert instanceURI != null : "instanceURI must not be null"; return find( repositoryConnection, clazz, new URIImpl(instanceURI.toString())); } /** Finds and loads the RDF entity from propositions in the knowledge base given its id string, without clearing * the dictionary of connected RDF entities. * * @param <T> the entity type * @param repositoryConnection the repository connection * @param clazz the RDF entity class * @param idString the id string that represents the RDF entity * @return the RDF entity or null if not found */ public <T> T find( final RepositoryConnection repositoryConnection, final Class<T> clazz, final String idString) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; // NOPMD assert clazz != null : "clazz must not be null"; assert idString != null : "idString must not be null"; assert !idString.isEmpty() : "idString must not be empty"; return find( repositoryConnection, clazz, new URIImpl(idString)); } /** Finds and loads RDF entities having the given RDF predicate, RDF value and class. * * @param <T> the entity type * @param repositoryConnection the repository connection * @param predicate the given RDF predicate * @param rdfValue the RDF value of the predicate * @param rdfEntityClass the class of the desired RDF entities * @return the RDF entities having the given RDF predicate and RDF value */ public <T> List<T> find( final RepositoryConnection repositoryConnection, final URI predicate, final Value rdfValue, final Class<T> rdfEntityClass) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; assert predicate != null : "predicate must not be null"; assert rdfValue != null : "rdfValue must not be null"; assert rdfEntityClass != null : "rdfEntityClass must not be null"; // NOPMD return find( repositoryConnection, predicate, rdfValue, null, // override context rdfEntityClass); } /** Finds and loads RDF entities having the given RDF predicate, RDF value and class. * * @param <T> the entity type * @param repositoryConnection the repository connection * @param predicate the given RDF predicate * @param rdfValue the RDF value of the predicate * @param overrideContextURI the override context, or null if the default context is to be used * @param rdfEntityClass the class of the desired RDF entities * @return the RDF entities having the given RDF predicate and RDF value */ public <T> List<T> find( final RepositoryConnection repositoryConnection, final URI predicate, final Value rdfValue, final URI overrideContextURI, final Class<T> rdfEntityClass) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; assert predicate != null : "predicate must not be null"; assert rdfValue != null : "rdfValue must not be null"; assert rdfEntityClass != null : "rdfEntityClass must not be null"; // NOPMD final List<T> rdfEntities = new ArrayList<>(); connectedRDFEntityDictionary.clear(); initializeAbstractSessionState(); setRDFEntityClass(rdfEntityClass); gatherAnnotationsForRDFEntityClass(); setOverrideContextURI(overrideContextURI); configureRDFEntitySettings(); final List<URI> instanceURIs = findInstanceURIsByPredicateAndValue( repositoryConnection, predicate, rdfValue, rdfEntityClass); for (final URI instanceURI : instanceURIs) { rdfEntities.add(findRDFEntity( repositoryConnection, rdfEntityClass, instanceURI)); } return rdfEntities; } /** Gets the default context of the given persistent class. * * @param rdfEntityClass the persistent class * @return the default context */ public synchronized URI getDefaultContext(final Class<?> rdfEntityClass) { //Preconditions assert rdfEntityClass != null : "rdfEntityClass must not be null"; // static dictionary is guarded by the synchronization of this method URI contextURI1 = DEFAULT_CONTEXT_DICTIONARY.get(rdfEntityClass.getName()); if (contextURI1 == null) { connectedRDFEntityDictionary.clear(); initializeAbstractSessionState(); setRDFEntityClass(rdfEntityClass); gatherAnnotationsForRDFEntityClass(); setOverrideContextURI(null); configureRDFEntitySettings(); contextURI1 = getContextURI(); DEFAULT_CONTEXT_DICTIONARY.put(rdfEntityClass.getName(), contextURI1); } return contextURI1; } /** Loads the given lazy RDF entity field. * * @param repositoryConnection the repository connection * @param rdfEntity the RDF entity * @param field the field to be loaded * @param rdfProperty the RDF property the associates field value(s) in the knowledge base * @param predicateValuesDictionary the predicate values dictionary, predicate --> RDF values * @return the field value */ public Object loadLazyRDFEntityField( final RepositoryConnection repositoryConnection, final RDFPersistent rdfEntity, final Field field, final RDFProperty rdfProperty, final Map<URI, List<Value>> predicateValuesDictionary) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; assert rdfEntity != null : "domainInstance must not be null"; assert field != null : "field must not be null"; // NOPMD assert rdfProperty != null : "rdfProperty must not be null"; // NOPMD if (isDebugEnabled) { getLogger().debug(stackLevel() + "lazy loading RDF entity entity " + rdfEntity.getClass().getName() + "\n field: " + field + "\n rdfProperty: " + rdfProperty); } initializeAbstractSessionState(); setRDFEntityClass(rdfEntity.getClass()); setRDFEntity(rdfEntity); gatherAnnotationsForRDFEntityClass(); configureRDFEntitySettings(); setInstanceURIFromIdField(); final Object value; try { value = loadField( repositoryConnection, field, rdfProperty, predicateValuesDictionary); } catch (final TexaiException ex) { getLogger().warn(StringUtils.getStackTraceAsString(ex)); throw new TexaiException(ex.getMessage() + "\npredicateValuesDictionary: " + predicateValuesDictionary, ex); } return value; } /** Returns an iterator over the set of instances of the given RDF entity class. * * @param <T> the entity type * @param repositoryConnection the repository connection * @param rdfEntityClass the given RDF entity class * @param overrideContextURI the override context * @return an iterator over the set of instances of the given RDF entity class */ public <T> Iterator<T> rdfEntityIterator( final RepositoryConnection repositoryConnection, final Class<T> rdfEntityClass, final URI overrideContextURI) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; assert rdfEntityClass != null : "rdfEntityClass must not be null"; return new RDFEntityIterator<>(repositoryConnection, rdfEntityClass, overrideContextURI); } /** Provides an iterator over the instances of the specified RDF entity class. * * @param <E> the specified RDF entity class */ public final class RDFEntityIterator<E> implements Iterator<E> { /** the repository connection */ private final RepositoryConnection repositoryConnection; /** the RDF entity class */ private final Class<?> rdfEntityClass; /** the iterator over the set of instances of the given RDF entity class */ private final Iterator<URI> rdfEntityURISet_iter; /** the effective context */ private final URI effectiveContextURI; /** Constructs a new RDFEntityIterator instance. * * @param repositoryConnection the repository connection * @param rdfEntityClass the given RDF entity class * @param overrideContextURI the override context */ public RDFEntityIterator( final RepositoryConnection repositoryConnection, final Class<?> rdfEntityClass, final URI overrideContextURI) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; assert rdfEntityClass != null : "rdfEntityClass must not be null"; this.repositoryConnection = repositoryConnection; this.rdfEntityClass = rdfEntityClass; saveAbstractSessionState(); saveSessionState(); connectedRDFEntityDictionary.clear(); initializeAbstractSessionState(); setRDFEntityClass(rdfEntityClass); gatherAnnotationsForRDFEntityClass(); setOverrideContextURI(overrideContextURI); configureRDFEntitySettings(); effectiveContextURI = getEffectiveContextURI(); assert effectiveContextURI != null; final Set<URI> rdfEntityURISet = new HashSet<>(); try { if (overrideContextURI == null || overrideContextURI.equals(getContextURI())) { // query the type statement final TupleQuery subjectsOfTypeTupleQuery = repositoryConnection.prepareTupleQuery( QueryLanguage.SERQL, "SELECT s FROM {s} rdf:type {o}"); subjectsOfTypeTupleQuery.setBinding("o", getClassURI()); final TupleQueryResult tupleQueryResult = subjectsOfTypeTupleQuery.evaluate(); while (tupleQueryResult.hasNext()) { rdfEntityURISet.add((URI) tupleQueryResult.next().getBinding("s").getValue()); } tupleQueryResult.close(); } else { // query the overrideContext statement final TupleQuery subjectsOfTypeTupleQuery = repositoryConnection.prepareTupleQuery( QueryLanguage.SERQL, "SELECT s FROM {s} p {o}"); subjectsOfTypeTupleQuery.setBinding("p", URI_OVERRIDE_CONTEXT); subjectsOfTypeTupleQuery.setBinding("o", overrideContextURI); final TupleQueryResult tupleQueryResult = subjectsOfTypeTupleQuery.evaluate(); while (tupleQueryResult.hasNext()) { rdfEntityURISet.add((URI) tupleQueryResult.next().getBinding("s").getValue()); } tupleQueryResult.close(); } } catch (final OpenRDFException ex) { throw new TexaiException(ex); } restoreSessionState(); restoreAbstractSessionState(); rdfEntityURISet_iter = rdfEntityURISet.iterator(); } /** Returns <tt>true</tt> if the iteration has more elements. (In other * words, returns <tt>true</tt> if <tt>next</tt> would return an element * rather than throwing an exception.) * * @return <tt>true</tt> if the iterator has more elements. */ @Override public boolean hasNext() { return rdfEntityURISet_iter.hasNext(); } /** Returns the next element in the iteration. Calling this method * repeatedly until the {@link #hasNext()} method returns false will * return each element in the underlying collection exactly once. * * @return the next element in the iteration. */ @SuppressWarnings("unchecked") @Override public E next() { if (!rdfEntityURISet_iter.hasNext()) { throw new NoSuchElementException("iterator is empty and cannot return the next element"); } final URI rdfEntityURI = rdfEntityURISet_iter.next(); saveAbstractSessionState(); saveSessionState(); connectedRDFEntityDictionary.clear(); setEffectiveContextURI(effectiveContextURI); assert getEffectiveContextURI() != null; final Object rdfEntity = findRDFEntity( repositoryConnection, rdfEntityClass, rdfEntityURI); assert rdfEntity != null : "rdfEntity not found: " + rdfEntityURI + ", class: " + rdfEntityClass; restoreAbstractSessionState(); restoreSessionState(); return (E) rdfEntity; } /** Removes from the underlying collection the last element returned by the * iterator (optional operation). This method can be called only once per * call to <tt>next</tt>. The behavior of an iterator is unspecified if * the underlying collection is modified while the iteration is in * progress in any way other than by calling this method. */ @Override public void remove() { rdfEntityURISet_iter.remove(); } /** Returns a string representation of this object. * * @return a string representation of this object */ @Override public String toString() { return "[iterator over instances of " + rdfEntityClass.getName() + "]"; } } /** Gets the logger. * * @return the logger */ @Override protected Logger getLogger() { return logger; } /** Finds and loads the RDF entity from propositions in the knowledge base given its instance URI, without clearing * the dictionary of connected RDF entities. If the given class is null then it is looked up using the URI. * * @param <T> the class type * @param repositoryConnection the repository connection * @param clazz the RDF entity class * @param instanceURI the URI that represents the RDF entity * @return the RDF entity or null if not found */ @SuppressWarnings("unchecked") private <T> T findRDFEntity( final RepositoryConnection repositoryConnection, final Class<T> clazz, final URI instanceURI) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; assert instanceURI != null : "instanceURI must not be null"; if (isDebugEnabled) { getLogger().debug(stackLevel() + "loading RDF entity instance URI: " + instanceURI); } initializeAbstractSessionState(); Class<?> rdfEntityClass = clazz; if (clazz == null) { rdfEntityClass = getJavaClass(instanceURI); if (rdfEntityClass == null) { throw new TexaiException("cannot determine the RDF entity class from the URI " + instanceURI); } } setRDFEntityClass(rdfEntityClass); setInstanceURI(instanceURI); predicateValuesDictionary = queryForPredicateAndValues(repositoryConnection); if (predicateValuesDictionary.isEmpty()) { if (isDebugEnabled) { getLogger().debug(stackLevel() + "no predicateValuesDictionary for instance URI: " + instanceURI); } setRDFEntity(null); } else { //validatePredicateValuesDictionary(instanceURI.toString()); gatherAnnotationsForRDFEntityClass(); configureRDFEntitySettings(); instantiateRDFEntity(); loadIdField(); loadFields(repositoryConnection); if (isDebugEnabled) { getLogger().debug(stackLevel() + "caching connected RDF entity: " + instanceURI + "-->" + getRDFEntity().getClass().getName()); } } return (T) getRDFEntity(); } /** Returns the set of instance URIs having the given RDF predicate and RDF value. * * @param repositoryConnection the repository connection * @param predicate the identifying RDF predicate * @param rdfValue the RDF value of the predicate which identifies the desired RDF entity * @param rdfEntityClass the class of the desired RDF entity * @return the set of instance URIs having the given RDF predicate, RDF value and given class */ private List<URI> findInstanceURIsByPredicateAndValue( final RepositoryConnection repositoryConnection, final URI predicate, final Value rdfValue, final Class<?> rdfEntityClass) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; assert predicate != null : "predicate must not be null"; assert rdfValue != null : "value must not be null"; assert rdfEntityClass != null : "rdfEntityClass must not be null"; final String rdfEntityClassName = rdfEntityClass.getName(); final List<URI> instanceURIs = new ArrayList<>(); try { final TupleQuery subjectsTupleQuery = repositoryConnection.prepareTupleQuery( QueryLanguage.SERQL, "SELECT s, c FROM CONTEXT c {s} p {o}"); subjectsTupleQuery.setBinding("p", predicate); subjectsTupleQuery.setBinding("o", rdfValue); final TupleQueryResult tupleQueryResult = subjectsTupleQuery.evaluate(); while (tupleQueryResult.hasNext()) { final BindingSet bindingSet = tupleQueryResult.next(); final URI instanceURI = (URI) bindingSet.getBinding("s").getValue(); final URI contextURI1 = (URI) bindingSet.getBinding("c").getValue(); final String className = getJavaClass(instanceURI).getName(); if (className == null || className.isEmpty()) { throw new TexaiException("missing class name for URI " + instanceURI); } if (className.equals(rdfEntityClassName) && getEffectiveContextURI().equals(contextURI1)) { instanceURIs.add(instanceURI); } } tupleQueryResult.close(); } catch (final OpenRDFException ex) { throw new TexaiException(ex); } return instanceURIs; } /** Sets the instance URI from the id field contained in the RDF entity. */ private void setInstanceURIFromIdField() { //Preconditions assert getRDFEntity() != null : "rdfEntity() must not be null"; if (getRDFEntity() instanceof RDFPersistent) { setInstanceURI((getRDFEntity()).getId()); return; } final Field idField = getIdField(); if (idField == null) { throw new TexaiException("Id field not found for RDF entity " + getRDFEntity().getClass().getName()); } Object value; if (!idField.isAccessible()) { idField.setAccessible(true); } try { value = idField.get(getRDFEntity()); } catch (final IllegalArgumentException | IllegalAccessException ex) { throw new TexaiException(ex); } if (value instanceof String || value instanceof java.net.URI) { setInstanceURI(getValueFactory().createURI(value.toString())); } else if (URI.class.isAssignableFrom(value.getClass())) { setInstanceURI((URI) value); } else { throw new TexaiException("cannot load ID from " + value); } if (isDebugEnabled) { getLogger().debug(stackLevel() + " found instance URI " + getInstanceURI()); } } /** Instantiates the RDF entity. */ private void instantiateRDFEntity() { //Preconditions assert getRDFEntityClass() != null : "rdfEntityClass must not be null"; assert getInstanceURI() != null : "instanceURI must not be null"; RDFPersistent rdfEntity; if (isDebugEnabled) { getLogger().debug(stackLevel() + "instantiating: " + getRDFEntityClass().getName() + " for " + getInstanceURI()); } try { rdfEntity = (RDFPersistent) getRDFEntityClass().newInstance(); } catch (final IllegalAccessException ex) { throw new TexaiException(ex); } catch (final InstantiationException ex) { throw new TexaiException("exception instantiating: " + getRDFEntityClass().getName() + " for " + getInstanceURI() + "\n RDF entity classes require a default constructor" + "\n This error can also be caused by an invalid @RDFEntity subject annotation.", ex); } setRDFEntity(rdfEntity); connectedRDFEntityDictionary.put(getInstanceURI(), getRDFEntity()); } /** Loads the id field of the RDF entity. */ private void loadIdField() { //Preconditions 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); } try { final Class<?> idFieldType = idField.getType(); if (idFieldType.equals(String.class)) { idField.set(getRDFEntity(), getInstanceURI().toString()); } else if (idFieldType.equals(java.net.URI.class)) { idField.set(getRDFEntity(), new java.net.URI(getInstanceURI().toString())); } else if (URI.class.isAssignableFrom(idFieldType)) { idField.set(getRDFEntity(), getInstanceURI()); } else { throw new TexaiException("cannot load id for " + getInstanceURI() + " into ID field type " + idFieldType.getName()); } } catch (final IllegalArgumentException ex) { throw new TexaiException(ex.getMessage() + "\n rdfEntity: " + getRDFEntity() + "\n instanceURI: " + getInstanceURI(), ex); } catch (final URISyntaxException ex) { throw new TexaiException(ex); } catch (final IllegalAccessException ex) { throw new TexaiException(ex); } } /** Loads the RDF entity fields from propositions in the knowledge base. * * @param repositoryConnection the repository connection */ private void loadFields(final RepositoryConnection repositoryConnection) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; assert getInstanceURI() != null : "instanceURI must not be null"; for (final Field field : getFieldAnnotationDictionary().keySet()) { final Annotation annotation = getFieldAnnotationDictionary().get(field); if (isDebugEnabled) { getLogger().debug(stackLevel() + "loading field: " + field + ", annotation: " + annotation); } if ("@javax.persistence.Id()".equals(annotation.toString())) { if (isDebugEnabled) { getLogger().debug(stackLevel() + " skipping Id field"); } continue; } else { if (annotation instanceof RDFProperty) { final Class<?> fieldType = field.getType(); final RDFProperty rdfProperty = (RDFProperty) annotation; if (rdfProperty.fetch().equals(FetchType.EAGER)) { loadField( repositoryConnection, field, rdfProperty, predicateValuesDictionary); } else { // default behavior is lazy loading if (Set.class.isAssignableFrom(fieldType)) { if (isDebugEnabled) { getLogger().debug(stackLevel() + " lazySet for field: " + field); } final LazySet lazySet = new LazySet( repositoryConnection, getRDFEntity(), field, rdfProperty, predicateValuesDictionary); setFieldValue(field, lazySet, fieldType, repositoryConnection); } else if (List.class.isAssignableFrom(fieldType)) { if (isDebugEnabled) { getLogger().debug(stackLevel() + " lazyList for field: " + field); } final LazyList lazyList = new LazyList( repositoryConnection, getRDFEntity(), field, rdfProperty, predicateValuesDictionary); setFieldValue(field, lazyList, fieldType, repositoryConnection); } else if (Map.class.isAssignableFrom(fieldType)) { if (isDebugEnabled) { getLogger().debug(stackLevel() + " lazyMap for field: " + field); } final LazyMap lazyMap = new LazyMap( repositoryConnection, getRDFEntity(), field, rdfProperty, predicateValuesDictionary); setFieldValue(field, lazyMap, fieldType, repositoryConnection); } else if (isRDFEntityClass(fieldType)) { setToDynmicallyCreatedProxy( repositoryConnection, field, fieldType, rdfProperty, predicateValuesDictionary); } else { // otherwise load the value now loadField( repositoryConnection, field, rdfProperty, predicateValuesDictionary); } } } } } } /** Sets the field to a dynamically created proxy, using cglib that will lazily load the RDF entity field. If the URI value * is null then the field is left null. * * @param repositoryConnection the repository connection * @param field the field * @param fieldType the field type, which is an RDF entity * @param rdfProperty the RDF property annotation that describes the field * @param predicateValuesDictionary the predicate values dictionary, predicate --> RDF values */ private void setToDynmicallyCreatedProxy( final RepositoryConnection repositoryConnection, final Field field, final Class<?> fieldType, final RDFProperty rdfProperty, final Map<URI, List<Value>> predicateValuesDictionary) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; assert field != null : "field must not be null"; assert fieldType != null : "fieldType must not be null"; assert rdfProperty != null : "rdfProperty must not be null"; assert predicateValuesDictionary != null : "predicateValuesDictionary must not be null"; assert getEffectiveContextURI() != null : "predicateValuesDictionary must not be null"; // see if there is a URI value for this field final URI predicate; predicate = getEffectivePropertyURI(field, rdfProperty); List<Value> rdfValues; if (rdfProperty.inverse()) { try { rdfValues = new ArrayList<>(); final TupleQuery subjectsTupleQuery = repositoryConnection.prepareTupleQuery( QueryLanguage.SERQL, "SELECT s, c FROM CONTEXT c {s} p {o}"); subjectsTupleQuery.setBinding("p", predicate); subjectsTupleQuery.setBinding("o", getInstanceURI()); //subjectsTupleQuery.setBinding("c", getEffectiveContextURI()); final TupleQueryResult tupleQueryResult = subjectsTupleQuery.evaluate(); while (tupleQueryResult.hasNext()) { final BindingSet bindingSet = tupleQueryResult.next(); final URI contextURI1 = (URI) bindingSet.getBinding("c").getValue(); if (getEffectiveContextURI().equals(contextURI1)) { rdfValues.add(bindingSet.getBinding("s").getValue()); } } tupleQueryResult.close(); } catch (final OpenRDFException ex) { throw new TexaiException(ex); } } else { rdfValues = predicateValuesDictionary.get(predicate); } if (rdfValues == null || rdfValues.isEmpty()) { // no URI value for this field, so no need for a proxy to lazily load it if (isDebugEnabled) { getLogger().debug(stackLevel() + " URI value not found, field is null: " + field); } return; } //TODO cannot use a dynamic proxy on the android platform if (isDebugEnabled) { getLogger().debug(stackLevel() + " lazyily loaded proxy for field: " + field); } // load the value upon first access to the associated proxy final RDFEntityLazyLoader rdfEntityLazyLoader = new RDFEntityLazyLoader( repositoryConnection, getRDFEntity(), field, rdfProperty, predicateValuesDictionary); // dynamically create the proxy using cglib Object lazyObjectProxy; final Factory factory = proxyFactoryDictionary.get(fieldType); if (factory == null) { lazyObjectProxy = Enhancer.create(fieldType, rdfEntityLazyLoader); proxyFactoryDictionary.put(fieldType, (Factory) lazyObjectProxy); } else { lazyObjectProxy = factory.newInstance(rdfEntityLazyLoader); } setFieldValue(field, lazyObjectProxy, fieldType, repositoryConnection); } /** Queries for the predicate and RDF rdfValues of matching RDF triples having the subject filled by the instanceURI. * * @param repositoryConnection the repository connection * @return the list of predicate and RDF value */ private Map<URI, List<Value>> queryForPredicateAndValues(final RepositoryConnection repositoryConnection) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; assert getInstanceURI() != null : "instance uri must not be null in " + getRDFEntity(); final Map<URI, List<Value>> tempPredicateValuesDictionary = new HashMap<>(); try { final TupleQuery predicatesAndObjectsTupleQuery = repositoryConnection.prepareTupleQuery( QueryLanguage.SERQL, "SELECT p, o FROM {s} p {o}"); predicatesAndObjectsTupleQuery.setBinding("s", getInstanceURI()); final TupleQueryResult tupleQueryResult = predicatesAndObjectsTupleQuery.evaluate(); while (tupleQueryResult.hasNext()) { final BindingSet bindingSet = tupleQueryResult.next(); final URI predicate = (URI) bindingSet.getBinding("p").getValue(); List<Value> rdfValues = tempPredicateValuesDictionary.get(predicate); if (rdfValues == null) { rdfValues = new ArrayList<>(); tempPredicateValuesDictionary.put(predicate, rdfValues); } rdfValues.add(bindingSet.getBinding("o").getValue()); } tupleQueryResult.close(); } catch (final RepositoryException | MalformedQueryException | QueryEvaluationException ex) { getLogger().error("repositoryConnection: " + repositoryConnection); throw new TexaiException(ex); } return tempPredicateValuesDictionary; } /** Loads the given field according to the given RDF property. * * @param repositoryConnection the repository connection * @param field the given RDF entity instance field * @param rdfProperty the property annotation associated with the field * @param predicateValuesDictionary the predicate values dictionary, predicate --> RDF values * @return the loaded value */ @SuppressWarnings("unchecked") private Object loadField( final RepositoryConnection repositoryConnection, final Field field, final RDFProperty rdfProperty, final Map<URI, List<Value>> predicateValuesDictionary) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; assert field != null : "field must not be null"; assert rdfProperty != null : "rdfProperty must not be null"; assert predicateValuesDictionary != null : "predicateValuesDictionary must not be null"; this.predicateValuesDictionary = predicateValuesDictionary; // obtain field attributes final Class<?> fieldType = field.getType(); if (isDebugEnabled) { getLogger().debug(stackLevel() + " field: " + field); getLogger().debug(stackLevel() + " field type: " + fieldType.getName()); } final List<Object> values = getValues( repositoryConnection, field, rdfProperty, predicateValuesDictionary); final int values_size = values.size(); // load the value according to its field type Object loadedValue = null; if (fieldType.isArray()) { // load an array field final Object array = Array.newInstance(fieldType.getComponentType(), values_size); for (int i = 0; i < values_size; i++) { Array.set(array, i, values.get(i)); } loadedValue = array; setFieldValue( field, loadedValue, fieldType, repositoryConnection); } else if (fieldType.equals(List.class)) { // load a List field final List<Object> list = new ArrayList<>(); for (final Object value : values) { list.add(value); } loadedValue = list; setFieldValue(field, loadedValue, fieldType, repositoryConnection); } else if (fieldType.equals(Map.class)) { // load a Map field final Map<Object, Object> map = new HashMap<>(); for (final Object value : values) { final MapEntry mapEntry = (MapEntry) value; map.put(mapEntry.key, mapEntry.value); } loadedValue = map; setFieldValue(field, loadedValue, fieldType, repositoryConnection); } else if (Collection.class.isAssignableFrom(fieldType)) { // load a Collection field Collection<Object> collection; Class<?> concreteFieldType; if (fieldType.equals(Collection.class)) { concreteFieldType = ArrayList.class; // NOPMD } else if (fieldType.equals(HashSet.class)) { // NOPMD // if the field specifies HashSet then instantiate one, otherwise for Set fields instantiate an ArraySet // which is more efficient for construction and iteration concreteFieldType = HashSet.class; // NOPMD } else if (fieldType.equals(Set.class)) { concreteFieldType = ArraySet.class; } else { concreteFieldType = fieldType; } try { collection = (Collection<Object>) concreteFieldType.newInstance(); } catch (final InstantiationException | IllegalAccessException ex) { throw new TexaiException(ex); } for (final Object value : values) { collection.add(value); } loadedValue = collection; setFieldValue(field, loadedValue, fieldType, repositoryConnection); } else { // load a single value field if (values_size > 1) { getLogger().info(stackLevel() + "expected only one value for field " + field + " but found\n " + values + " for instanceURI " + getInstanceURI()); } if (values.isEmpty()) { if (isDebugEnabled) { getLogger().debug(stackLevel() + " field has no values to load " + field); } } else { loadedValue = values.get(0); setFieldValue(field, loadedValue, fieldType, repositoryConnection); } } return loadedValue; } /** Gets the values for loading the given field. * * @param repositoryConnection the repository connection * @param field the given RDF entity instance field * @param rdfProperty the property annotation associated with the field * @param predicateValuesDictionary the predicate values dictionary, predicate --> RDF values * @return the unordered list of values for the field */ private List<Object> getValues( final RepositoryConnection repositoryConnection, final Field field, final RDFProperty rdfProperty, final Map<URI, List<Value>> predicateValuesDictionary) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; assert field != null : "field must not be null"; assert rdfProperty != null : "rdfProperty must not be null"; assert predicateValuesDictionary != null : "predicateValuesDictionary must not be null"; List<Object> values; if (isDebugEnabled) { getLogger().debug(stackLevel() + " processing RDF property: " + rdfProperty); } final boolean isBooleanField = "boolean".equals(field.getType().getName()); URI predicate; if (isBooleanField) { predicate = RDF.TYPE; } else { predicate = getEffectivePropertyURI(field, rdfProperty); } if (!field.isAccessible()) { field.setAccessible(true); } final Class<?> fieldType = field.getType(); if (rdfProperty.inverse()) { if (isBooleanField) { throw new TexaiException("the inverse annotation is not applicable to boolean fields"); } values = queryForInverseValues( repositoryConnection, predicate, fieldType, rdfProperty.inverse()); } else { List<Value> rdfValues = predicateValuesDictionary.get(predicate); if (rdfValues == null) { return new ArrayList<>(0); // NOPMD } // if the field is a List or an array then the value is the blank node that heads the actual value list if (List.class.isAssignableFrom(fieldType) || fieldType.isArray()) { assert rdfValues.size() == 1 : "only one blank node must be present " + rdfValues + " in " + repositoryConnection.getRepository().getDataDir(); assert rdfValues.get( 0) instanceof BNode : "RDF value must be a blank node " + rdfValues.get(0); final BNode rdfListHead = (BNode) rdfValues.get(0); rdfValues = getRDFListValues(repositoryConnection, rdfListHead); } else if (Map.class.isAssignableFrom(fieldType)) { // if the field is a Map then the RDF values are the blank nodes that each relates a map entry key and map entry value return getMapValues(repositoryConnection, rdfValues, rdfProperty); } // transform the RDF values to java objects values = new ArrayList<>(rdfValues.size()); for (final Value rdfValue : rdfValues) { if (isBooleanField) { values.add(rdfValue); } else { values.add(getJavaValueFromRDFValue( repositoryConnection, rdfValue, field.getType())); } } } if (isBooleanField) { if (isDebugEnabled) { getLogger().debug(stackLevel() + " boolean field: " + field.getName()); } // for boolean values, return a true value if the trueClass is among the queried values, otherwise return a false value String trueClassName = rdfProperty.trueClass(); if (trueClassName.isEmpty()) { // default true class trueClassName = getRDFEntity().getClass().getName() + "_" + field.getName() + "_True"; } final URI trueClass = makeURI(trueClassName); String falseClassName = rdfProperty.falseClass(); if (falseClassName.isEmpty()) { // default false class falseClassName = getRDFEntity().getClass().getName() + "_" + field.getName() + "_False"; } final URI falseClass = makeURI(falseClassName); // note that there will only be one boolean value returned final List<Object> booleanValues = new ArrayList<>(1); for (final Object value : values) { if (isDebugEnabled) { getLogger().debug(stackLevel() + " " + value + " equals " + trueClass + " ?"); } if (value.equals(trueClass)) { booleanValues.add(Boolean.TRUE); return booleanValues; // NOPMD } } for (final Object value : values) { if (isDebugEnabled) { getLogger().debug(stackLevel() + " " + value + " equals " + trueClass + " ?"); } if (value.equals(falseClass)) { booleanValues.add(Boolean.FALSE); return booleanValues; // NOPMD } } if (isDebugEnabled) { getLogger().debug(stackLevel() + " boolean field true/false class not found for : " + field.getName() + ", defaulting to false"); } booleanValues.add(Boolean.FALSE); values = booleanValues; } return values; } /** Gathers the MapEntry objects corresponding to the given blank node RDF values that each relates a persisted * map entry key and map entry value. * * @param repositoryConnection the repository connection * @param rdfValues the given RDF values * @param rdfProperty the RDFProperty annotation of the field to be persisted * @return the MapEntry objects corresponding to the given blank node RDF values */ public List<Object> getMapValues( final RepositoryConnection repositoryConnection, final List<Value> rdfValues, final RDFProperty rdfProperty) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; assert rdfValues != null : "rdfValues must not be null"; assert rdfProperty != null : "rdfProperty must not be null"; final List<Object> values = new ArrayList<>(); for (final Value rdfValue : rdfValues) { assert rdfValue instanceof BNode; final MapEntry mapEntry = MapEntry.makeMapEntry( (BNode) rdfValue, // bNode repositoryConnection, getEffectiveContextURI()); // get the type of the map key final String mapKeyTypeName = rdfProperty.mapKeyType(); if (mapKeyTypeName == null || mapKeyTypeName.isEmpty()) { throw new TexaiException("Map field @RDFProperty annotation is missing its required mapKeyType property"); } final Class<?> mapKeyType; try { mapKeyType = Class.forName(mapKeyTypeName); } catch (ClassNotFoundException ex) { throw new TexaiException("The @RDFProperty mapKeyType class was not found: " + mapKeyTypeName); } // convert the key RDF value to the corresponding Java object mapEntry.key = getJavaValueFromRDFValue(repositoryConnection, mapEntry.keyRDFValue, mapKeyType); // get the type of the map value final String mapValueTypeName = rdfProperty.mapValueType(); if (mapValueTypeName == null || mapValueTypeName.isEmpty()) { throw new TexaiException("Map field @RDFProperty annotation is missing its required mapValueType property"); } final Class<?> mapValueType; try { mapValueType = Class.forName(mapValueTypeName); } catch (ClassNotFoundException ex) { throw new TexaiException("The @RDFProperty mapValueType class was not found: " + mapValueTypeName); } // convert the value RDF value to the corresponding Java object mapEntry.value = getJavaValueFromRDFValue(repositoryConnection, mapEntry.valueRDFValue, mapValueType); values.add(mapEntry); } return values; } /** Sets the given field to the given value if the value is not null. * * @param field the given field to be set * @param value the field value * @param fieldType the field type * @param repositoryConnection the repository connection */ private void setFieldValue(final Field field, final Object value, final Class<?> fieldType, final RepositoryConnection repositoryConnection) { //Preconditions assert field != null : "field must not be null"; assert fieldType != null : "fieldType must not be null"; if (value == null) { if (isDebugEnabled) { getLogger().debug(stackLevel() + " value is null"); } } else { if (isDebugEnabled) { getLogger().debug(stackLevel() + " load value (" + value.getClass().getName() + ") into fieldType " + fieldType.getName()); } if (!field.isAccessible()) { field.setAccessible(true); } try { field.set(getRDFEntity(), value); } catch (final IllegalArgumentException ex) { final StringBuilder stringBuilder = new StringBuilder(); try { final RepositoryResult<Statement> repositoryResult = repositoryConnection.getStatements( getInstanceURI(), // subj null, // pred null, // obj false); // includeInferred while (repositoryResult.hasNext()) { stringBuilder.append('\n'); stringBuilder.append(RDFUtility.formatStatement(repositoryResult.next())); } stringBuilder.append('\n'); } catch (RepositoryException ex1) { throw new TexaiException(ex1); } throw new TexaiException( ex.getMessage() + "\ninstanceURI: " + getInstanceURI() + "\nfield: " + field + "\nvalue: " + value + " value class: " + value.getClass().getName() + "\nfieldType: " + fieldType + "\nperhaps the field's value is an unpersisted new entity," + "\n or if the value has an incompatible type, suspect a corrupt repository" + "\nstatments:" + stringBuilder.toString(), ex); } catch (final IllegalAccessException ex) { throw new TexaiException( ex.getMessage() + "\nfield: " + field + "\nvalue: " + value + "\nfieldType: " + fieldType, ex); } } } /** Queries for the value terms filling the object position of matching RDF triples having the * given predicate, and the subject filled by the instanceURI. The RDF value terms are translated into java objects. * * @param repositoryConnection the repository connection * @param predicate the predicate that relates the value for the RDF entity field * @param fieldType the field type * @param isInverseProperty the indicator that the property is to be inverted with respect to treatment of subject and object * @return the list of java values */ private List<Object> queryForInverseValues( final RepositoryConnection repositoryConnection, final URI predicate, final Class<?> fieldType, final boolean isInverseProperty) { //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; assert predicate != null : "predicate must not be null"; assert fieldType != null : "fieldType must not be null"; List<Value> rdfValues = new ArrayList<>(); if (isInverseProperty) { try { final TupleQuery subjectsTupleQuery = repositoryConnection.prepareTupleQuery( QueryLanguage.SERQL, "SELECT s, c FROM CONTEXT c {s} p {o}"); subjectsTupleQuery.setBinding("p", predicate); subjectsTupleQuery.setBinding("o", getInstanceURI()); //subjectsTupleQuery.setBinding("c", getEffectiveContextURI()); final TupleQueryResult tupleQueryResult = subjectsTupleQuery.evaluate(); while (tupleQueryResult.hasNext()) { final BindingSet bindingSet = tupleQueryResult.next(); final URI contextURI1 = (URI) bindingSet.getBinding("c").getValue(); if (getEffectiveContextURI().equals(contextURI1)) { rdfValues.add(bindingSet.getBinding("s").getValue()); } } tupleQueryResult.close(); } catch (final OpenRDFException ex) { throw new TexaiException(ex); } } else { try { final TupleQuery objectsTupleQuery = repositoryConnection.prepareTupleQuery( QueryLanguage.SERQL, "SELECT o, c FROM CONTEXT c {s} p {o}"); objectsTupleQuery.setBinding("s", getInstanceURI()); objectsTupleQuery.setBinding("p", predicate); //objectsTupleQuery.setBinding("c", getEffectiveContextURI()); final TupleQueryResult tupleQueryResult = objectsTupleQuery.evaluate(); while (tupleQueryResult.hasNext()) { final BindingSet bindingSet = tupleQueryResult.next(); final URI contextURI1 = (URI) bindingSet.getBinding("c").getValue(); if (getEffectiveContextURI().equals(contextURI1)) { rdfValues.add(bindingSet.getBinding("o").getValue()); } } tupleQueryResult.close(); } catch (final OpenRDFException ex) { throw new TexaiException(ex); } } // if the field is a List or an array then the value is the blank node that heads the actual value list if (List.class.isAssignableFrom(fieldType) || fieldType.isArray()) { assert rdfValues.size() == 1 : "only one blank node must be present " + rdfValues; assert rdfValues.get( 0) instanceof BNode : "RDF value must be a blank node " + rdfValues.get(0); final BNode rdfListHead = (BNode) rdfValues.get(0); rdfValues = getRDFListValues(repositoryConnection, rdfListHead); } // transform RDF values to java objects final List<Object> values = new ArrayList<>(rdfValues.size()); for (final Value rdfValue : rdfValues) { values.add(getJavaValueFromRDFValue( repositoryConnection, rdfValue, fieldType)); } return values; } /** * Gets the java value from the given RDF value. In case of RDF entities, * state is saved and a recursive RDF entity load is performed. * * @param repositoryConnection the repository connection * @param rdfValue the RDF value * @param fieldType the field type * @return the java value from the given RDF value */ private Object getJavaValueFromRDFValue( final RepositoryConnection repositoryConnection, final Value rdfValue, final Class<?> fieldType) { // NOPMD //Preconditions assert repositoryConnection != null : "repositoryConnection must not be null"; assert rdfValue != null : "valueTerm must not be null"; Object value = null; if (fieldType.equals(URI.class)) { return rdfValue; } else if (rdfValue instanceof Literal) { if (fieldType.equals(Literal.class) || fieldType.equals(Value.class)) { return rdfValue; } final Literal literal = (Literal) rdfValue; if (fieldType.equals(UUID.class)) { value = UUID.fromString(literal.getLabel()); } else if (literal.getDatatype() == null || literal.getDatatype().equals(XMLSchema.STRING)) { value = literal.getLabel(); } else if (literal.getDatatype().equals(XMLSchema.BYTE) || literal.getDatatype().equals(XMLSchema.UNSIGNED_BYTE)) { value = literal.byteValue(); } else if (literal.getDatatype().equals(XMLSchema.SHORT) || literal.getDatatype().equals(XMLSchema.UNSIGNED_SHORT)) { value = literal.shortValue(); } else if (literal.getDatatype().equals(XMLSchema.INT) || literal.getDatatype().equals(XMLSchema.UNSIGNED_INT)) { value = literal.intValue(); } else if (literal.getDatatype().equals(XMLSchema.LONG) || literal.getDatatype().equals(XMLSchema.UNSIGNED_LONG)) { if (int.class.equals(fieldType) || Integer.class.equals(fieldType)) { // legacy value = literal.intValue(); } else { value = literal.longValue(); } } else if (literal.getDatatype().equals(XMLSchema.FLOAT)) { value = literal.floatValue(); } else if (literal.getDatatype().equals(XMLSchema.DOUBLE)) { value = literal.doubleValue(); } else if (literal.getDatatype().equals(XMLSchema.INTEGER) || literal.getDatatype().equals(XMLSchema.POSITIVE_INTEGER) || literal.getDatatype().equals(XMLSchema.NON_NEGATIVE_INTEGER) || literal.getDatatype().equals(XMLSchema.NON_POSITIVE_INTEGER) || literal.getDatatype().equals(XMLSchema.NEGATIVE_INTEGER)) { value = literal.integerValue(); } else if (literal.getDatatype().equals(XMLSchema.DECIMAL)) { value = literal.decimalValue(); } else if (literal.getDatatype().equals(XMLSchema.DATETIME)) { final Calendar calendarValue = DatatypeConverter.parseDateTime(literal.getLabel()); if (fieldType.equals(Date.class)) { value = calendarValue.getTime(); } else if (fieldType.equals(Calendar.class)) { value = calendarValue; } else if (fieldType.equals(DateTime.class)) { value = new DateTime(calendarValue); } else { throw new TexaiException("cannot load " + literal + " into field type " + fieldType.getName()); } } else if (literal.getDatatype().equals(XMLSchema.ANYURI)) { try { value = new java.net.URI(literal.getLabel()); } catch (final URISyntaxException ex) { throw new TexaiException("cannot form URI from " + literal, ex); } } return value; // NOPMD } if (fieldType.isAssignableFrom(rdfValue.getClass())) { return rdfValue; } if (Collection.class.isAssignableFrom(fieldType) || fieldType.isArray()) { if (rdfValue instanceof BNode) { // the value is the blank node that heads the list of actual values return rdfValue; } assert rdfValue instanceof URI : "rdfValue must be type URI"; if (getJavaClass((URI) rdfValue) == null) { // URI is not an RDF entity id return rdfValue; } } value = connectedRDFEntityDictionary.get((URI) rdfValue); if (value == null) { if (isDebugEnabled) { getLogger().debug(stackLevel() + " RDF value: " + rdfValue); } // save this state before the recursive method call saveAbstractSessionState(); saveSessionState(); final DistributedRepositoryManager distributedRepositoryManager = DistributedRepositoryManager.getInstance(); // obtain a new repository connection according to where the entity is persisted final String repositoryName = distributedRepositoryManager.getRepositoryNameForInstance((URI) rdfValue); if (repositoryName == null) { // the field type may be a superclass of the RDF entity, therefore do not pass it as a parameter value = find(repositoryConnection, (URI) rdfValue); } else { final RepositoryConnection repositoryConnection2 = distributedRepositoryManager.getRepositoryConnectionForRepositoryName(repositoryName); // the field type may be a superclass of the RDF entity, therefore do not pass it as a parameter value = find(repositoryConnection2, (URI) rdfValue); try { repositoryConnection2.close(); } catch (final RepositoryException ex) { throw new TexaiException(ex); } } if (value == null) { // domain entity not found when treating the rdfValue as an ID value = rdfValue; } restoreSessionState(); restoreAbstractSessionState(); } else { if (isDebugEnabled) { getLogger().debug(stackLevel() + " previously loaded value: " + value); } } if (isDebugEnabled) { getLogger().debug(stackLevel() + " RDF value " + rdfValue + " ==> java value " + value); } return value; } /** Determines the java class of the RDF entity indentified by the given URI. * * @param uri the URI that identifies an RDF entity * @return the java class of the RDF entity indentified by the given URI */ private Class<?> getJavaClass(final URI uri) { //preconditions assert uri != null : "uri must not be null"; Class<?> clazz; // attempt to parse the class name from the URI final String parsedClassName = parseClassNameFromURI(uri); if (parsedClassName == null) { // URI is not an RDF entity id return null; } // does the class name have a cached class? clazz = classNameDictionary.get(parsedClassName); if (clazz == null) { try { // try to lookup the class name parsed from the URI clazz = Class.forName(parsedClassName); classNameDictionary.put(parsedClassName, clazz); } catch (final ClassNotFoundException ex) { throw new TexaiException("cannot find entity class " + parsedClassName + " from malformed URI " + uri, ex); } if (isDebugEnabled) { getLogger().debug(stackLevel() + " URI: " + uri + " parsed class: " + clazz); } } return clazz; } /** Returns the class name parsed from the given URI. * * @param uri the given URI * @return the class name parsed from the given URI if it has the format of an RDF entity id, otherwise returns null */ private String parseClassNameFromURI(final URI uri) { //preconditions assert uri != null : "uri must not be null"; final String localName = uri.getLocalName(); int index = -1; for (int i = localName.length() - 1; i >= 0; i--) { if (localName.charAt(i) == '_') { index = i; break; } } if (index == -1) { return null; } else { final String className = localName.substring(0, index); if (className.startsWith("org.texai.")) { return className; } else { // handle legacy WordNet URIs return "org.texai.wordnet.domain.entity." + className; } } } /** Pushes the current session bean state onto a stack and then initializes the session state. */ public void saveSessionState() { //Preconditions assert rdfEntityInfoStack != null : "rdfEntityInfoStack must not be null"; if (isDebugEnabled) { getLogger().debug(stackLevel() + "saving session state"); } rdfEntityInfoStack.push(new RDFEntityInfo(connectedRDFEntityDictionary, predicateValuesDictionary)); } /** 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(); connectedRDFEntityDictionary = rdfEntityInfo.connectedRDFEntityDictionary; predicateValuesDictionary = rdfEntityInfo.predicateValuesDictionary; if (isDebugEnabled) { getLogger().debug(stackLevel() + "restored session state"); } } /** Contains the state for recursive method calls. */ private static class RDFEntityInfo { /** the dictionary of connected RDF entities, URI --> RDF instance */ private final Map<URI, Object> connectedRDFEntityDictionary; // NOPMD /** the predicate values dictionary, predicate --> RDF values */ private final Map<URI, List<Value>> predicateValuesDictionary; /** Creates a new RDFEntityInfo instance. * * @param connectedRDFEntityDictionary the dictionary of connected RDF entities, URI --> RDF instance * @param predicateValuesDictionary the predicate values dictionary, predicate --> RDF values */ protected RDFEntityInfo( final Map<URI, Object> connectedRDFEntityDictionary, final Map<URI, List<Value>> predicateValuesDictionary) { //Preconditions assert connectedRDFEntityDictionary != null : "connectedRDFEntityDictionary must not be null"; this.connectedRDFEntityDictionary = connectedRDFEntityDictionary; this.predicateValuesDictionary = predicateValuesDictionary; } } }