/* * Copyright (c) 2010 Mysema Ltd. * All rights reserved. * */ package com.mysema.rdfbean.object; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Objects; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.mysema.commons.l10n.support.LocaleUtil; import com.mysema.commons.lang.Assert; import com.mysema.commons.lang.CloseableIterator; import com.mysema.commons.lang.IteratorAdapter; import com.mysema.query.types.EntityPath; import com.mysema.rdfbean.CORE; import com.mysema.rdfbean.annotations.ContainerType; import com.mysema.rdfbean.model.BID; import com.mysema.rdfbean.model.Blocks; import com.mysema.rdfbean.model.ID; import com.mysema.rdfbean.model.IDType; import com.mysema.rdfbean.model.Identifier; import com.mysema.rdfbean.model.LID; import com.mysema.rdfbean.model.LIT; import com.mysema.rdfbean.model.NODE; import com.mysema.rdfbean.model.QNODE; import com.mysema.rdfbean.model.QueryLanguage; import com.mysema.rdfbean.model.RDF; import com.mysema.rdfbean.model.RDFBeanTransaction; import com.mysema.rdfbean.model.RDFConnection; import com.mysema.rdfbean.model.RDFQuery; import com.mysema.rdfbean.model.RDFQueryImpl; import com.mysema.rdfbean.model.RDFS; import com.mysema.rdfbean.model.STMT; import com.mysema.rdfbean.model.UID; import com.mysema.rdfbean.ontology.Ontology; import com.mysema.rdfbean.query.BeanQueryImpl; import com.mysema.util.BeanMap; import com.mysema.util.MultimapFactory; /** * Default implementation of the Session interface * * @author sasa * @author tiwe * */ public final class SessionImpl implements Session { private static final Set<UID> CONTAINER_TYPES = new HashSet<UID>(Arrays.<UID> asList( RDF.Alt, RDF.Seq, RDF.Bag, RDFS.Container)); private static final int DEFAULT_INITIAL_CAPACITY = 1024; private static final Logger logger = LoggerFactory.getLogger(SessionImpl.class); private Set<STMT> addedStatements; private final Configuration configuration; private final RDFConnection connection; private final ErrorHandler errorHandler = new DefaultErrorHandler(); private FlushMode flushMode = FlushMode.ALWAYS; private final IdentityService identityService; private Multimap<ID, Object> instanceCache; private Map<ID, Map<String, NODE>> listCache; private final Iterable<Locale> locales; private final Ontology ontology; private final Map<String, ObjectRepository> parentRepositories = new HashMap<String, ObjectRepository>(); private Set<STMT> removedStatements; private Map<Object, ID> resourceCache; @Nullable private Set<Object> seen; @Nullable private RDFBeanTransaction transaction; public SessionImpl(Configuration configuration, Ontology ontology, RDFConnection connection, Iterable<Locale> locales) { this.configuration = configuration; this.ontology = ontology; this.connection = connection; this.locales = locales; this.identityService = new SessionIdentityService(connection); clear(); } public SessionImpl(Configuration configuration, Ontology ontology, RDFConnection connection, Locale... locales) { this(configuration, ontology, connection, Arrays.asList(locales)); } @Override public void addParent(String ns, ObjectRepository parent) { Assert.hasText(ns, "ns"); Assert.notNull(parent, "parent"); parentRepositories.put(ns, parent); } private <T> T assertHasIdProperty(T instance) { MappedClass mappedClass = configuration.getMappedClass(instance.getClass()); if (mappedClass.getIdProperty() == null) { throw new IllegalArgumentException(instance.getClass().getName() + " has no id property"); } return instance; } private <T> T assertMapped(T instance) { if (!configuration.isMapped(instance.getClass())) { throw new IllegalArgumentException(instance.getClass().getName() + " is not mapped"); } return instance; } private ID assignId(MappedClass mappedClass, BeanMap instance) { ID subject = createResource(mappedClass.getUID(), instance); setId(mappedClass, subject, instance); return subject; } @Override public void autowire(Object instance) { Assert.notNull(instance, "instance"); BeanMap beanMap = toBeanMap(instance); MappedClass mappedClass = configuration.getMappedClass(getClass(instance)); bind(mappedClass, getId(mappedClass, beanMap), beanMap, new PropertiesMap()); } @Override public RDFBeanTransaction beginTransaction() { return beginTransaction(false, -1, java.sql.Connection.TRANSACTION_READ_COMMITTED); } @Override public RDFBeanTransaction beginTransaction(boolean readOnly, int txTimeout, int isolationLevel) { if (transaction != null) { throw new IllegalStateException("Transaction exists already"); } transaction = connection.beginTransaction(readOnly, txTimeout, isolationLevel); return transaction; } private <T> T bind(MappedClass mappedClass, ID subject, T instance, PropertiesMap properties) { if (instance instanceof LifeCycleAware) { ((LifeCycleAware) instance).beforeBinding(); } // TODO: defaultContext parameter? UID context = getContext(instance, subject, null); BeanMap beanMap = toBeanMap(instance); // MappedClass mappedClass = // configuration.getMappedClass(getClass(instance)); setId(mappedClass, subject, beanMap); // loadStack.add(instance); if (!mappedClass.getDynamicProperties().isEmpty()) { bindDynamicProperties(subject, properties.getDirect(), beanMap, mappedClass); } for (MappedPath path : mappedClass.getProperties()) { if (!path.isConstructorParameter()) { MappedProperty<?> property = path.getMappedProperty(); if (!property.isVirtual()) { Object convertedValue; try { convertedValue = getValue(path, getPathValue(path, subject, properties, context), context); } catch (InstantiationException e) { throw new SessionException(e); } catch (IllegalAccessException e) { throw new SessionException(e); } if (convertedValue != null) { property.setValue(beanMap, convertedValue); } } } } if (instance instanceof LifeCycleAware) { ((LifeCycleAware) instance).afterBinding(); } return instance; } @SuppressWarnings("unchecked") private void bindDynamicProperties(ID subject, Multimap<UID, STMT> properties, BeanMap beanMap, MappedClass mappedClass) { for (MappedProperty<?> property : mappedClass.getDynamicProperties()) { UID context = property.getContext(); Map<UID, Object> values = new HashMap<UID, Object>(); for (STMT stmt : properties.values()) { if (stmt.getPredicate().equals(CORE.localId)) { // skip local ids continue; } if (context != null && !context.equals(stmt.getContext())) { // skip context mismatch continue; } if (property.isIncludeMapped() || !mappedClass.isMappedPredicate(stmt.getPredicate())) { Class<?> componentType; if (property.isDynamicCollection()) { componentType = property.getDynamicCollectionComponentType(); } else { componentType = property.getComponentType(); } // for resources make sure that componentType is compatible // with the resource if (stmt.getObject().isResource()) { if (!ID.class.isAssignableFrom(componentType)) { List<STMT> typeStmts = findStatements(stmt.getObject().asResource(), RDF.type, null, null, false); boolean matched = false; for (STMT typeStmt : typeStmts) { for (MappedClass cl : configuration.getMappedClasses(typeStmt.getObject().asURI())) { if (componentType.isAssignableFrom(cl.getJavaClass())) { matched = true; } } } if (!matched) { continue; } } // for literals, make sure that componentType is a // literal type } else { UID dataType = configuration.getConverterRegistry().getDatatype(componentType); if (dataType == null || !stmt.getObject().asLiteral().getDatatype().equals(dataType)) { continue; } } Object value = convertValue(stmt.getObject(), componentType); if (value != null) { if (property.isDynamicCollection()) { Collection<Object> collection = (Collection<Object>) values.get(stmt.getPredicate()); if (collection == null) { try { collection = property.getDynamicCollectionType().newInstance(); values.put(stmt.getPredicate(), collection); } catch (InstantiationException e) { throw new SessionException(e); } catch (IllegalAccessException e) { throw new SessionException(e); } } collection.add(value); } else { values.put(stmt.getPredicate(), value); } } } } property.setValue(beanMap, values); } } @Override public void clear() { // instanceCache = LazyMap.decorate(new HashMap<ID, // List<Object>>(DEFAULT_INITIAL_CAPACITY), LIST_FACTORY); instanceCache = HashMultimap.create(); resourceCache = new IdentityHashMap<Object, ID>(DEFAULT_INITIAL_CAPACITY); addedStatements = new LinkedHashSet<STMT>(DEFAULT_INITIAL_CAPACITY); removedStatements = new LinkedHashSet<STMT>(DEFAULT_INITIAL_CAPACITY); listCache = new LinkedHashMap<ID, Map<String, NODE>>(DEFAULT_INITIAL_CAPACITY); seen = null; } @Override public void close() { connection.close(); } @SuppressWarnings("unchecked") private Object convertClassReference(NODE value, MappedPath propertyPath, MappedProperty mappedProperty) { if (value instanceof UID) { return convertClassReference((UID) value, mappedProperty.getComponentType()); } else { throw new BindException(propertyPath, "bnode or literal", value); } } @Nullable private Class<?> convertClassReference(UID uid, Class<?> targetClass) { List<MappedClass> mappedClasses = configuration.getMappedClasses(uid); boolean foundMatch = false; for (MappedClass mappedClass : mappedClasses) { Class<?> clazz = mappedClass.getJavaClass(); if (targetClass.isAssignableFrom(clazz)) { targetClass = clazz; foundMatch = true; } } if (foundMatch) { return targetClass; } else { return null; } } @SuppressWarnings("unchecked") private Object convertCollection(MappedPath propertyPath, Collection<? extends NODE> values, UID context) throws InstantiationException, IllegalAccessException { MappedProperty<?> mappedProperty = propertyPath.getMappedProperty(); Class<?> targetType = mappedProperty.getComponentType(); int size = values.size(); if (size == 1) { NODE node = values.iterator().next(); if (node instanceof ID) { if (mappedProperty.isList()) { values = convertList((ID) node, context); } else if (mappedProperty.isContainer()) { values = convertContainer((ID) node, context, mappedProperty.isIndexed()); } } // TODO else log error? } // TODO else log error? Class collectionType = mappedProperty.getCollectionType(); Collection collection = (Collection) collectionType.newInstance(); for (NODE value : values) { if (value != null) { collection.add(convertValue(value, targetType, propertyPath)); } else { collection.add(null); } } return collection; } @SuppressWarnings("unchecked") private Object convertArray(MappedPath propertyPath, Collection<? extends NODE> values, UID context) throws InstantiationException, IllegalAccessException { MappedProperty<?> mappedProperty = propertyPath.getMappedProperty(); Class<?> targetType = mappedProperty.getComponentType(); int size = values.size(); if (size == 1) { NODE node = values.iterator().next(); if (node instanceof ID) { if (mappedProperty.isList()) { values = convertList((ID) node, context); } else if (mappedProperty.isContainer()) { values = convertContainer((ID) node, context, mappedProperty.isIndexed()); } } // TODO else log error? } // TODO else log error? Object array = Array.newInstance(targetType, values.size()); int i = 0; for (NODE value : values) { if (value != null) { Array.set(array, i++, convertValue(value, targetType, propertyPath)); } else { Array.set(array, i++, null); } } return array; } private Collection<NODE> convertContainer(ID node, UID context, boolean indexed) { List<STMT> stmts = findStatements(node, null, null, context, false); Map<Integer, NODE> values = new LinkedHashMap<Integer, NODE>(); int maxIndex = 0; int i = 0; for (STMT stmt : stmts) { i++; UID predicate = stmt.getPredicate(); if (RDF.NS.equals(predicate.ns())) { String ln = predicate.ln(); int index = 0; if ("li".equals(ln)) { index = i; } else if (RDF.isContainerMembershipPropertyLocalName(ln)) { index = Integer.valueOf(ln.substring(1)); } if (index > 0) { maxIndex = Math.max(maxIndex, index); values.put(Integer.valueOf(index), stmt.getObject()); } } } if (indexed) { NODE[] nodes = new NODE[maxIndex]; for (Map.Entry<Integer, NODE> entry : values.entrySet()) { nodes[entry.getKey() - 1] = entry.getValue(); } return Arrays.asList(nodes); } else { return values.values(); } } @SuppressWarnings("unchecked") private Object convertEnum(NODE value, Class<?> targetClass) { if (value instanceof UID) { return Enum.valueOf((Class<? extends Enum>) targetClass, ((UID) value).ln()); } else if (value instanceof LIT) { return Enum.valueOf((Class<? extends Enum>) targetClass, value.getValue()); } else { throw new BindException("Cannot bind BNode into enum"); } } private Object convertIDReference(NODE value, MappedPath propertyPath) { if (value instanceof ID) { return value; } else { throw new BindException(propertyPath, value); } } private Collection<NODE> convertList(ID subject, UID context) { List<NODE> list = new ArrayList<NODE>(); while (subject != null && !subject.equals(RDF.nil)) { if (logger.isDebugEnabled()) { logger.debug("query for list elements of " + subject); } Map<String, NODE> nodes = listCache.get(subject); if (nodes == null) { nodes = new RDFQueryImpl(connection) .where(Blocks.S_REST, Blocks.optional(Blocks.S_FIRST)) .set(QNODE.s, subject) .selectSingle(QNODE.first, QNODE.rest); } if (nodes == null) { if (list.size() == 0) { list.add(subject); } break; } NODE value = nodes.get(QNODE.first.getName()); if (value != null) { list.add(value); } else { list.add(null); } subject = (ID) nodes.get(QNODE.rest.getName()); } return list; } private String convertLocalized(MappedPath propertyPath, Set<? extends NODE> values) { return LocaleUtil.getLocalized(convertLocalizedMap(propertyPath, values), locales, null); } private Map<Locale, String> convertLocalizedMap(MappedPath propertyPath, Set<? extends NODE> values) { Map<Locale, String> result = new HashMap<Locale, String>(); for (NODE value : values) { if (value.isLiteral()) { LIT literal = (LIT) value; Locale lang = literal.getLang(); if (lang == null) { lang = Locale.ROOT; } result.put(lang, literal.getValue()); } else { throw new IllegalArgumentException("Expected Literal, got " + value.getNodeType()); } } return result; } @SuppressWarnings("unchecked") private Object convertMap(MappedPath propertyPath, Set<? extends NODE> values) { MappedProperty<?> propertyDefinition = propertyPath.getMappedProperty(); Object convertedValue; Class<?> componentType = propertyDefinition.getComponentType(); Class<?> keyType = propertyDefinition.getKeyType(); Map map = new HashMap(); for (NODE value : values) { // Map key Object key = convertValue( getFunctionalValue((ID) value, propertyDefinition.getKeyPredicate(), false, null), keyType, propertyPath); // Map Value Object mapValue; UID valuePredicate = propertyDefinition.getValuePredicate(); if (valuePredicate != null) { mapValue = convertValue(getFunctionalValue((ID) value, valuePredicate, false, null), componentType, propertyPath); } else { mapValue = convertValue(value, componentType, propertyPath); } map.put(key, mapValue); } convertedValue = map; return convertedValue; } @SuppressWarnings("unchecked") private Object convertMappedClass(NODE value, Class<?> targetClass, MappedPath propertyPath, MappedProperty mappedProperty) { if (value instanceof ID) { return convertMappedObject((ID) value, targetClass, isPolymorphic(mappedProperty), mappedProperty.isInjection()); } else { throw new BindException(propertyPath, value); } } @SuppressWarnings("unchecked") @Nullable private <T> T convertMappedObject(ID subject, Class<T> requiredClass, boolean polymorphic, boolean injection) { UID context = getContext(requiredClass, subject, null); Object instance = getCached(subject, requiredClass); if (instance == null) { if (injection) { if (subject instanceof UID && requiredClass != null) { UID uri = (UID) subject; ObjectRepository orepo = parentRepositories.get(uri.ns()); if (orepo != null) { return orepo.getBean(requiredClass, uri); } else { throw new IllegalArgumentException("No such parent repository: " + uri.ns()); } } } else if (requiredClass.isEnum() && subject.isURI()) { return (T) Enum.valueOf((Class) requiredClass, subject.asURI().ln()); } MappedClass mappedClass = configuration.getMappedClass(requiredClass); Multimap<UID, STMT> direct = getProperties(subject, mappedClass, polymorphic); if (!direct.isEmpty()) { Multimap<UID, STMT> inverse = null; if (mappedClass.getInvMappedPredicates().isEmpty()) { inverse = MultimapFactory.<UID, STMT> create(); } else { inverse = getInvProperties(subject, mappedClass); } instance = getMappedObject(mappedClass, subject, requiredClass, new PropertiesMap(direct, inverse), polymorphic, context, true); } } return (T) instance; } @SuppressWarnings("unchecked") @Nullable private Object convertSingleValue(MappedPath propertyPath, Set<? extends NODE> values) { MappedProperty<?> propertyDefinition = propertyPath.getMappedProperty(); Class targetType = propertyDefinition.getType(); if (!values.isEmpty()) { return convertValue(values.iterator().next(), targetType, propertyPath); } else { return null; } } private Object convertValue(NODE node, Class<?> targetClass) { UID targetType = configuration.getConverterRegistry().getDatatype(targetClass); if (targetClass.isAssignableFrom(node.getClass())) { return node; } else if (targetType != null && node.isLiteral()) { // TODO : make sure this works also with untyped literals etc if (((LIT) node).getDatatype().equals(targetType)) { return configuration.getConverterRegistry().fromString(node.getValue(), targetClass); } else { throw new IllegalArgumentException("Literal " + node + " is not of type " + targetType); } } else if (targetType == null && node.isURI()) { return get(targetClass, node.asURI()); } else { throw new IllegalArgumentException("Node " + node + " could not be converted to " + targetClass.getName()); } } @SuppressWarnings("unchecked") @Nullable private Object convertValue(NODE value, Class<?> targetClass, MappedPath propertyPath) { Object convertedValue; MappedProperty mappedProperty = propertyPath.getMappedProperty(); try { if (targetClass.isAssignableFrom(value.getClass())) { convertedValue = value; } // "Wildcard" type else if (MappedPath.isWildcard(targetClass) && value.isResource()) { convertedValue = convertMappedObject((ID) value, Object.class, true, mappedProperty.isInjection()); } // Enumerations else if (targetClass.isEnum()) { convertedValue = convertEnum(value, targetClass); } // Class reference else if (mappedProperty.isClassReference()) { convertedValue = convertClassReference(value, propertyPath, mappedProperty); } // Mapped class else if (configuration.isMapped(targetClass) || mappedProperty.isInjection()) { convertedValue = convertMappedClass(value, targetClass, propertyPath, mappedProperty); } // ID reference else if (ID.class.isAssignableFrom(targetClass)) { convertedValue = convertIDReference(value, propertyPath); } // Use standard property editors for others else { // UID datatype = null; // if (value instanceof LIT) { // datatype = ((LIT) value).getDatatype(); // } convertedValue = configuration.getConverterRegistry().fromString(value.getValue(), targetClass); } } catch (IllegalArgumentException e) { if (propertyPath.isIgnoreInvalid()) { logger.debug(e.getMessage(), e); convertedValue = null; } else { logger.error(e.getMessage(), e); convertedValue = errorHandler.conversionError(value, targetClass, propertyPath, e); } } return convertedValue; } @Nullable private <T> T createInstance(ID subject, Class<T> requiredType, Collection<ID> mappedTypes, PropertiesMap properties) { T instance; Class<? extends T> actualType = matchType(mappedTypes, requiredType); if (actualType != null) { if (!configuration.allowCreate(actualType)) { instance = null; } else { try { MappedClass mappedClass = configuration.getMappedClass(actualType); MappedConstructor mappedConstructor = mappedClass.getConstructor(); if (mappedConstructor == null) { instance = actualType.newInstance(); } else { List<Object> constructorArguments = getConstructorArguments(mappedClass, subject, properties, mappedConstructor); @SuppressWarnings("unchecked") Constructor<T> constructor = (Constructor<T>) mappedConstructor.getConstructor(); instance = constructor.newInstance(constructorArguments.toArray()); } } catch (InstantiationException e) { logger.error(e.getMessage(), e); instance = errorHandler.createInstanceError(subject, mappedTypes, requiredType, e); } catch (IllegalAccessException e) { logger.error(e.getMessage(), e); instance = errorHandler.createInstanceError(subject, mappedTypes, requiredType, e); } catch (SecurityException e) { logger.error(e.getMessage(), e); instance = errorHandler.createInstanceError(subject, mappedTypes, requiredType, e); } catch (IllegalArgumentException e) { logger.error(e.getMessage(), e); instance = errorHandler.createInstanceError(subject, mappedTypes, requiredType, e); } catch (InvocationTargetException e) { logger.error(e.getMessage(), e); instance = errorHandler.createInstanceError(subject, mappedTypes, requiredType, e); } } } else { instance = errorHandler.typeMismatchError(subject, mappedTypes, requiredType); } return instance; } private <T> Map<ID, T> createInstances(MappedClass mappedClass, Class<T> clazz, boolean polymorphic, UID context, Map<ID, Multimap<UID, STMT>> directProps, Map<ID, Multimap<UID, STMT>> inverseProps) { Map<ID, T> idToInstance = new HashMap<ID, T>(directProps.size()); for (Map.Entry<ID, Multimap<UID, STMT>> entry : directProps.entrySet()) { T instance = getCached(entry.getKey(), clazz); if (instance == null) { PropertiesMap properties = new PropertiesMap(entry.getValue(), inverseProps.get(entry.getKey())); instance = getMappedObject(mappedClass, entry.getKey(), clazz, properties, polymorphic, context, false); if (instance != null) { idToInstance.put(entry.getKey(), instance); } } } return idToInstance; } private RDFQuery createQuery(MappedClass mappedClass, @Nullable UID type, boolean polymorphic) { RDFQuery query = new RDFQueryImpl(connection); if (type != null) { query.where(Blocks.S_TYPE); if (mappedClass.getContext() != null) { query.set(QNODE.typeContext, mappedClass.getContext()); } if (polymorphic) { Collection<UID> types = ontology.getSubtypes(type); if (types.size() > 1 && connection.getInferenceOptions().subClassOf()) { query.where(QNODE.type.in(types)); } else { query.set(QNODE.type, type); } } else { query.set(QNODE.type, type); } } query.where(Blocks.SPOC); if (!polymorphic && mappedClass.getDynamicProperties().isEmpty() && mappedClass.getMappedPredicates().size() < 5) { query.where(QNODE.p.in(mappedClass.getMappedPredicates())); } return query; } @Override public <D, Q> Q createQuery(QueryLanguage<D, Q> queryLanguage, D definition) { return connection.createQuery(queryLanguage, definition); } @Override public <Q> Q createQuery(QueryLanguage<Void, Q> queryLanguage) { return connection.createQuery(queryLanguage, null); } private ID createResource(@Nullable UID type, BeanMap instance) { ID id = configuration.createURI(instance.getBean()); if (id == null) { // String base = type != null ? type.getLocalName() : "Resource"; // String local = UUID.randomUUID().toString().replace("-", ""); // id = new UID("resource://" + base + "/" + local); id = connection.createBNode(); } return id; } @Override public void delete(Object instance) { deleteInternal(assertMapped(instance)); if (flushMode == FlushMode.ALWAYS) { flush(); } } @Override public void delete(Class<?> clazz, ID subject) { UID context = getContext(clazz, subject, null); deleteResource(subject, context); if (flushMode == FlushMode.ALWAYS) { flush(); } } @Override public void deleteAll(Object... objects) { for (Object object : objects) { deleteInternal(assertMapped(object)); } if (flushMode == FlushMode.ALWAYS) { flush(); } } @Override public void deleteAll(Class<?> clazz, ID... subjects) { for (ID subject : subjects) { UID context = getContext(clazz, subject, null); deleteResource(subject, context); } if (flushMode == FlushMode.ALWAYS) { flush(); } } private void deleteInternal(Object instance) { BeanMap beanMap = toBeanMap(instance); ID subject = resourceCache.get(instance); Class<?> clazz = getClass(instance); MappedClass mappedClass = configuration.getMappedClass(clazz); if (subject == null) { subject = getId(mappedClass, beanMap); } if (subject != null) { UID context = getContext(clazz, subject, null); deleteResource(subject, context); } } private void deleteResource(ID subject, @Nullable UID context) { // Delete own properties for (STMT statement : findStatements(subject, null, null, context, false)) { recordRemoveStatement(statement); NODE object = statement.getObject(); if (object.isResource() && !statement.getPredicate().equals(RDF.type)) { removeList((ID) object, context); removeContainer((ID) object, context); } } // Delete references for (STMT statement : findStatements(null, null, subject, context, false)) { recordRemoveStatement(statement); } // Remove from primary cache Collection<Object> instances = instanceCache.removeAll(subject); if (instances != null) { for (Object obj : instances) { resourceCache.remove(obj); } } } private boolean exists(ID subject, MappedClass mappedClass, UID context) { UID type = mappedClass.getUID(); if (type != null) { return connection.exists(subject, RDF.type, type, context, true); } return false; } private Set<NODE> filterObjects(List<STMT> statements) { Set<NODE> objects = new LinkedHashSet<NODE>(); for (STMT statement : statements) { objects.add(statement.getObject()); } return objects; } @SuppressWarnings("unchecked") private <T extends NODE> Set<T> filterSubject(List<STMT> statements) { Set<T> subjects = new LinkedHashSet<T>(); for (STMT statement : statements) { subjects.add((T) statement.getSubject()); } return subjects; } @Override public <T> List<T> findInstances(Class<T> clazz) { UID type = configuration.getMappedClass(clazz).getUID(); if (type != null) { Set<T> instances = new LinkedHashSet<T>(); findInstances(clazz, type, instances); return new ArrayList<T>(instances); } else { throw new IllegalArgumentException("No RDF type specified for " + clazz.getName()); } } @Override public <T> List<T> findInstances(Class<T> clazz, LID lid) { ID id = identityService.getID(lid); if (id instanceof UID) { return findInstances(clazz, (UID) id); } else { throw new IllegalArgumentException("Blank nodes not supported"); } } @Override public <T> List<T> findInstances(Class<T> clazz, UID uri) { final Set<T> instances = new LinkedHashSet<T>(); findInstances(clazz, uri, instances); return new ArrayList<T>(instances); } private <T> void findInstances(Class<T> clazz, UID type, final Set<T> instances) { MappedClass mappedClass = configuration.getMappedClass(clazz); boolean polymorphic = isPolymorphic(mappedClass); UID context = mappedClass.getContext(); if (logger.isDebugEnabled()) { logger.debug("query for " + clazz.getSimpleName() + " instance data"); } RDFQuery query = createQuery(mappedClass, type, polymorphic); CloseableIterator<STMT> stmts = query.construct(Blocks.SPOC); Map<ID, Multimap<UID, STMT>> directProps = getPropertiesMap(stmts, false); // no results if (directProps.isEmpty()) { return; } Map<ID, Multimap<UID, STMT>> inverseProps = Collections.emptyMap(); if (!polymorphic && !mappedClass.getInvMappedPredicates().isEmpty()) { inverseProps = getInvProperties(mappedClass, directProps.keySet()); } // create Map<ID, T> idToInstance = createInstances(mappedClass, clazz, polymorphic, context, directProps, inverseProps); // load references loadReferences(mappedClass, directProps, directProps.keySet()); // bind for (Map.Entry<ID, Multimap<UID, STMT>> entry : directProps.entrySet()) { T instance = getCached(entry.getKey(), clazz); if (idToInstance.containsKey(entry.getKey())) { PropertiesMap properties = new PropertiesMap(entry.getValue(), inverseProps.get(entry.getKey())); MappedClass mc = resolveMappedClass(mappedClass, properties); bind(mc, entry.getKey(), instance, properties); } instances.add(instance); } } private MappedClass resolveMappedClass(MappedClass mappedClass, PropertiesMap properties) { for (STMT stmt : properties.getDirect().get(RDF.type)) { if (stmt.getObject().isURI()) { List<MappedClass> mappedClasses = configuration.getMappedClasses(stmt.getObject().asURI()); for (MappedClass mc : mappedClasses) { if (!mc.equals(mappedClass) && mappedClass.getJavaClass().isAssignableFrom(mc.getJavaClass())) { return mc; } } } } return mappedClass; } private List<ID> findMappedTypes(ID subject, UID context, Multimap<UID, STMT> properties) { List<ID> types = new ArrayList<ID>(); if (properties.containsKey(RDF.type)) { for (STMT stmt : properties.get(RDF.type)) { NODE type = stmt.getObject(); if (type instanceof UID && configuration.getMappedClasses((UID) type) != null) { types.add((UID) type); } } } return types; } private Set<NODE> findPathValues(ID resource, MappedPath path, int index, PropertiesMap properties, UID context) { MappedPredicate predicate = path.get(index); if (predicate.getContext() != null) { context = predicate.getContext(); } Set<NODE> values; if (!predicate.inv() && properties.getDirect() != null) { values = findValues(predicate.getUID(), properties.getDirect(), context, predicate.inv()); } else if (predicate.inv() && properties.getInverse() != null) { values = findValues(predicate.getUID(), properties.getInverse(), null, predicate.inv()); } else { values = findValues(resource, predicate.getUID(), predicate.inv(), predicate.includeInferred(), context); } if (path.size() > index + 1) { Set<NODE> nestedValues = new LinkedHashSet<NODE>(); for (NODE value : values) { if (value.isResource()) { nestedValues.addAll(findPathValues((ID) value, path, index + 1, new PropertiesMap(null, null), context)); } } return nestedValues; } return values; } private List<STMT> findStatements(@Nullable ID subject, @Nullable UID predicate, @Nullable NODE object, @Nullable UID context, boolean includeInferred) { if (logger.isDebugEnabled()) { logger.debug("findStatements " + subject + " " + predicate + " " + object + " " + context); } // rdf type inference if (RDF.type.equals(predicate) && subject == null && object != null && connection.getInferenceOptions().subClassOf()) { Collection<UID> types = ontology.getSubtypes(object.asURI()); if (types.size() > 1) { RDFQuery query = new RDFQueryImpl(connection); CloseableIterator<STMT> stmts = query.where( Blocks.SPOC, QNODE.o.in(types)) .set(QNODE.p, predicate) .construct(Blocks.SPOC); return IteratorAdapter.asList(stmts); } } return IteratorAdapter.asList(connection.findStatements(subject, predicate, object, context, includeInferred)); } private List<ID> findTypes(ID subject, UID context) { List<ID> types = new ArrayList<ID>(); List<STMT> statements = findStatements(subject, RDF.type, null, context, true); for (STMT stmt : statements) { types.add((ID) stmt.getObject()); } return types; } private Set<NODE> findValues(ID resource, UID predicate, boolean inverse, boolean includeInferred, UID context) { if (inverse) { return this.<NODE> filterSubject(findStatements(null, predicate, resource, context, includeInferred)); } else { return filterObjects(findStatements(resource, predicate, null, context, includeInferred)); } } private Set<NODE> findValues(UID predicate, Multimap<UID, STMT> properties, @Nullable UID context, boolean inv) { Set<NODE> nodes = new HashSet<NODE>(); if (properties.containsKey(predicate)) { for (STMT stmt : properties.get(predicate)) { if (context == null || context.equals(stmt.getContext())) { nodes.add(inv ? stmt.getSubject() : stmt.getObject()); } } } return nodes; } public void flush() { if (!removedStatements.isEmpty() || !addedStatements.isEmpty()) { connection.update(removedStatements, addedStatements); } removedStatements = new LinkedHashSet<STMT>(); addedStatements = new LinkedHashSet<STMT>(); } @Override public BeanQuery from(EntityPath<?>... expr) { return new BeanQueryImpl(this, ontology, connection).from(expr); } @Override public <T> T get(Class<T> clazz, ID subject) { Assert.notNull(subject, "subject"); boolean polymorphic = true; MappedClass mappedClass = configuration.getMappedClass(clazz); polymorphic = isPolymorphic(mappedClass); return convertMappedObject(subject, clazz, polymorphic, false); } @Override public <T> T get(Class<T> clazz, LID subject) { ID id = identityService.getID(subject); return id != null ? get(clazz, id) : null; } @Override public <T> List<T> getAll(Class<T> clazz, ID... subjects) { List<T> instances = new ArrayList<T>(subjects.length); if (!clazz.isEnum()) { Set<ID> ids = new HashSet<ID>(subjects.length); for (ID id : subjects) { if (id != null && getCached(id, clazz) == null) { ids.add(id); } } // return from cache if (ids.isEmpty()) { return getFromCache(clazz, instances, subjects); } MappedClass mappedClass = configuration.getMappedClass(clazz); boolean polymorphic = isPolymorphic(mappedClass); UID context = mappedClass.getContext(); if (logger.isDebugEnabled()) { logger.debug("query for " + clazz.getSimpleName() + " instance data"); } RDFQuery query = createQuery(mappedClass, null, polymorphic); query.where(QNODE.s.in(ids)); CloseableIterator<STMT> stmts = query.construct(Blocks.SPOC); Map<ID, Multimap<UID, STMT>> directProps = getPropertiesMap(stmts, false); // no results, return from cache if (directProps.isEmpty()) { return getFromCache(clazz, instances, subjects); } Map<ID, Multimap<UID, STMT>> inverseProps = Collections.emptyMap(); if (!polymorphic && !mappedClass.getInvMappedPredicates().isEmpty()) { inverseProps = getInvProperties(mappedClass, directProps.keySet()); } // create Map<ID, T> idToInstance = createInstances(mappedClass, clazz, polymorphic, context, directProps, inverseProps); // load references loadReferences(mappedClass, directProps, directProps.keySet()); // bind for (ID subject : subjects) { T instance = null; if (subject != null && directProps.containsKey(subject)) { instance = getCached(subject, clazz); if (idToInstance.containsKey(subject)) { PropertiesMap properties = new PropertiesMap(directProps.get(subject), inverseProps.get(subject)); bind(mappedClass, subject, instance, properties); } } instances.add(instance); } } else { for (ID subject : subjects) { if (subject != null) { instances.add(get(clazz, subject)); } else { instances.add(null); } } } return instances; } @Override public <T> List<T> getAll(Class<T> clazz, LID... subjects) { ID[] ids = new ID[subjects.length]; // TODO : bulk fetch for (int i = 0; i < ids.length; i++) { if (subjects[i] != null) { ids[i] = identityService.getID(subjects[i]); } } return getAll(clazz, ids); } @Override public <T> T getBean(Class<T> clazz, UID subject) { return get(clazz, subject); } @Override public <T> T getByExample(T entity) { return new ExampleQuery<T>(configuration, this, entity).uniqueResult(); } @Override public <T> T getById(String id, Class<T> clazz) { return get(clazz, new LID(id)); } @SuppressWarnings("unchecked") @Nullable private <T> T getCached(ID resource, Class<T> clazz) { for (Object instance : instanceCache.get(resource)) { if (clazz == null || clazz.isInstance(instance)) { return (T) instance; } } return null; } private Class<?> getClass(Object object) { return object instanceof BeanMap ? ((BeanMap) object).getBean().getClass() : object.getClass(); } @Override public Configuration getConfiguration() { return configuration; } public RDFConnection getConnection() { return connection; } private List<Object> getConstructorArguments(MappedClass mappedClass, ID subject, PropertiesMap properties, MappedConstructor mappedConstructor) throws InstantiationException, IllegalAccessException { List<Object> constructorArguments = new ArrayList<Object>(mappedConstructor.getArgumentCount()); // TODO parentContext? UID context = getContext(mappedConstructor.getDeclaringClass(), subject, null); for (MappedPath path : mappedConstructor.getMappedArguments()) { constructorArguments.add(getValue(path, getPathValue(path, subject, properties, context), context)); } return constructorArguments; } private UID getContext(Class<?> clazz, @Nullable ID subject, @Nullable UID defaultContext) { UID contextUID = configuration.getMappedClass(clazz).getContext(); if (contextUID != null) { return contextUID; } else { return defaultContext; } } private UID getContext(Object instance, @Nullable ID subject, @Nullable UID defaultContext) { return getContext(instance.getClass(), subject, defaultContext); } @Override public Locale getCurrentLocale() { if (locales != null) { Iterator<Locale> liter = locales.iterator(); if (liter.hasNext()) { return liter.next(); } } return Locale.ROOT; } @Override public FlushMode getFlushMode() { return flushMode; } private <T> List<T> getFromCache(Class<T> clazz, List<T> instances, ID... subjects) { for (ID id : subjects) { instances.add(id != null ? getCached(id, clazz) : null); } return instances; } @Nullable private NODE getFunctionalValue(ID subject, UID predicate, boolean includeInferred, @Nullable UID context) { List<STMT> statements = findStatements(subject, predicate, null, context, includeInferred); if (statements.size() > 1) { errorHandler.functionalValueError(subject, predicate, includeInferred, context); return statements.get(0).getObject(); } if (statements.size() > 0) { return statements.get(0).getObject(); } else { return null; } } @Nullable private ID getId(MappedClass mappedClass, Object instance) { MappedProperty<?> idProperty = mappedClass.getIdProperty(); if (idProperty != null) { // Assigned id Object id = idProperty.getValue(toBeanMap(instance)); if (id != null) { if (idProperty.getIDType() == IDType.LOCAL) { LID lid; if (id instanceof LID) { lid = (LID) id; } else { lid = new LID(id.toString()); } return identityService.getID(lid); } else { ID rid = null; if (id instanceof UID) { rid = (UID) id; } else if (idProperty.getIDType() == IDType.URI) { if (idProperty.getIDNamespace().isEmpty()) { rid = new UID(id.toString()); } else { rid = new UID(idProperty.getIDNamespace(), id.toString()); } } else { rid = (ID) id; } return rid; } } } return null; } @Override @SuppressWarnings("unchecked") public ID getId(Object instance) { if (instance instanceof LID) { return identityService.getID((LID) instance); } else { MappedClass mappedClass = configuration.getMappedClass(getClass(assertMapped(instance))); if (instance.getClass().isEnum()) { return new UID(mappedClass.getUID().ns(), ((Enum) instance).name()); } else { BeanMap beanMap = toBeanMap(instance); return getId(mappedClass, beanMap); } } } private Multimap<UID, STMT> getInvProperties(ID object, MappedClass mappedClass) { Multimap<UID, STMT> properties = MultimapFactory.<UID, STMT> create(); if (logger.isDebugEnabled()) { logger.debug("query for inverse properties of " + object); } RDFQuery query = new RDFQueryImpl(connection); CloseableIterator<STMT> stmts = query.where( Blocks.SPOC, QNODE.p.in(mappedClass.getInvMappedPredicates())) .set(QNODE.o, object) .construct(Blocks.SPOC); try { while (stmts.hasNext()) { STMT stmt = stmts.next(); properties.put(stmt.getPredicate(), stmt); } } finally { stmts.close(); } return properties; } private Map<ID, Multimap<UID, STMT>> getInvProperties(MappedClass mappedClass, Collection<ID> objects) { RDFQuery query = new RDFQueryImpl(connection); query.where( Blocks.SPOC, QNODE.p.in(mappedClass.getInvMappedPredicates())); if (objects.size() == 1) { query.set(QNODE.o, objects.iterator().next()); } else { query.where(QNODE.o.in(objects)); } CloseableIterator<STMT> stmts = query.construct(Blocks.SPOC); Map<ID, Multimap<UID, STMT>> invProperties = getPropertiesMap(stmts, true); for (ID id : objects) { if (!invProperties.containsKey(id)) { invProperties.put(id, MultimapFactory.<UID, STMT> create()); } } return invProperties; } @Override public LID getLID(ID id) { return identityService.getLID(id); } @Nullable private <T> T getMappedObject(MappedClass mappedClass, ID subject, Class<T> requiredClass, PropertiesMap properties, boolean polymorphic, UID context, boolean bind) { T instance = null; if (polymorphic) { Collection<ID> mappedTypes = findMappedTypes(subject, context, properties.getDirect()); if (!mappedTypes.isEmpty()) { instance = createInstance(subject, requiredClass, mappedTypes, properties); } else if (properties.getDirect().containsKey(RDF.rest)) { Map<String, NODE> values = new HashMap<String, NODE>(); values.put(RDF.rest.ln(), properties.getDirect().get(RDF.rest).iterator().next().getObject()); if (properties.getDirect().containsKey(RDF.first)) { values.put(RDF.first.ln(), properties.getDirect().get(RDF.first).iterator().next().getObject()); } listCache.put(subject, values); } else { logger.error("got no type for " + subject.getId()); } } else { instance = createInstance(subject, requiredClass, Collections.<ID> emptyList(), properties); } if (instance != null) { put(subject, instance); if (bind) { bind(mappedClass, subject, instance, properties); } } return instance; } private Set<NODE> getPathValue(MappedPath path, ID subject, PropertiesMap properties, UID context) { if (configuration.allowRead(path)) { Set<NODE> values; MappedProperty<?> property = path.getMappedProperty(); if (property.isMixin()) { values = Collections.<NODE> singleton(subject); } else if (path.size() > 0) { values = findPathValues(subject, path, 0, properties, context); } else { values = new LinkedHashSet<NODE>(); } if (values.isEmpty()) { for (UID uri : property.getDefaults()) { values.add(uri); } } return values; } return Collections.emptySet(); } private Multimap<UID, STMT> getProperties(ID subject, MappedClass mappedClass, boolean polymorphic) { Multimap<UID, STMT> properties = MultimapFactory.<UID, STMT> create(); if (mappedClass.getDynamicProperties().isEmpty() && !polymorphic && mappedClass.getMappedPredicates().size() < 5) { if (logger.isDebugEnabled()) { logger.debug("query for properties of " + subject); } RDFQuery query = new RDFQueryImpl(connection); CloseableIterator<STMT> stmts = query.where( Blocks.SPOC, QNODE.p.in(mappedClass.getMappedPredicates())) .set(QNODE.s, subject) .construct(Blocks.SPOC); try { while (stmts.hasNext()) { STMT stmt = stmts.next(); properties.put(stmt.getPredicate(), stmt); } } finally { stmts.close(); } } else { for (STMT stmt : findStatements(subject, null, null, null, true)) { properties.put(stmt.getPredicate(), stmt); } } return properties; } private Map<ID, Multimap<UID, STMT>> getPropertiesMap(CloseableIterator<STMT> stmts, boolean inv) { Map<ID, Multimap<UID, STMT>> propertiesMap = new HashMap<ID, Multimap<UID, STMT>>(); try { while (stmts.hasNext()) { STMT stmt = stmts.next(); ID key = inv ? stmt.getObject().asResource() : stmt.getSubject(); Multimap<UID, STMT> properties = propertiesMap.get(key); if (properties == null) { properties = MultimapFactory.<UID, STMT> create(); propertiesMap.put(key, properties); } properties.put(stmt.getPredicate(), stmt); } } finally { stmts.close(); } return propertiesMap; } @Override public RDFBeanTransaction getTransaction() { return transaction; } private Object getValue(MappedPath propertyPath, Set<? extends NODE> values, UID context) throws InstantiationException, IllegalAccessException { MappedProperty<?> mappedProperty = propertyPath.getMappedProperty(); Object convertedValue; // Collection if (mappedProperty.isCollection()) { convertedValue = convertCollection(propertyPath, values, context); } // Array else if (mappedProperty.isArray()) { convertedValue = convertArray(propertyPath, values, context); } // Localized else if (mappedProperty.isLocalized()) { if (mappedProperty.isMap()) { convertedValue = convertLocalizedMap(propertyPath, values); } else if (mappedProperty.getType().equals(String.class)) { convertedValue = convertLocalized(propertyPath, values); } else { throw new SessionException("Illegal use of @Localized with " + mappedProperty.getType() + " at " + propertyPath); } } // Map else if (mappedProperty.isMap()) { convertedValue = convertMap(propertyPath, values); } // Literal or *-to-one relation else if (values.size() <= 1 || propertyPath.isIgnoreInvalid()) { convertedValue = convertSingleValue(propertyPath, values); } // Unsupported type else { errorHandler.cardinalityError(propertyPath, values); convertedValue = convertSingleValue(propertyPath, values); } return convertedValue; } private boolean isContainer(ID node, UID context) { for (ID type : findTypes(node, context)) { if (CONTAINER_TYPES.contains(type)) { return true; } } return false; } private boolean isPolymorphic(MappedClass mappedClass) { return configuration.isPolymorphic(mappedClass.getJavaClass()); } private boolean isPolymorphic(MappedProperty<?> mappedProperty) { if (mappedProperty.isCollection() || mappedProperty.isMap()) { return configuration.isPolymorphic(mappedProperty.getComponentType()); } else { return configuration.isPolymorphic(mappedProperty.getType()); } } private <T> void loadAll(Class<T> clazz, Collection<ID> ids, Set<ID> handled) { MappedClass mappedClass = configuration.getMappedClass(clazz); boolean polymorphic = isPolymorphic(mappedClass); UID context = mappedClass.getContext(); if (logger.isDebugEnabled()) { logger.debug("query for " + clazz.getSimpleName() + " instance data"); } RDFQuery query = createQuery(mappedClass, null, polymorphic); query.where(QNODE.s.in(ids)); CloseableIterator<STMT> stmts = query.construct(Blocks.SPOC); Map<ID, Multimap<UID, STMT>> directProps = getPropertiesMap(stmts, false); if (directProps.isEmpty()) { return; } Map<ID, Multimap<UID, STMT>> inverseProps = Collections.emptyMap(); if (!polymorphic && !mappedClass.getInvMappedPredicates().isEmpty()) { inverseProps = getInvProperties(mappedClass, directProps.keySet()); } // create Map<ID, T> idToInstance = createInstances(mappedClass, clazz, polymorphic, context, directProps, inverseProps); // load references loadReferences(mappedClass, directProps, handled); for (Map.Entry<ID, Multimap<UID, STMT>> entry : directProps.entrySet()) { T instance = getCached(entry.getKey(), clazz); if (idToInstance.containsKey(entry.getKey())) { PropertiesMap properties = new PropertiesMap(entry.getValue(), inverseProps.get(entry.getKey())); MappedClass mc = resolveMappedClass(mappedClass, properties); bind(mc, entry.getKey(), instance, properties); } } } private void loadReferences(MappedClass mappedClass, Map<ID, Multimap<UID, STMT>> directProps, Set<ID> handled) { Map<UID, Class<?>> directToType = new HashMap<UID, Class<?>>(); for (MappedPath mappedPath : mappedClass.getProperties()) { if (mappedPath.isReference() && !mappedPath.getPredicatePath().isEmpty()) { MappedProperty<?> property = mappedPath.getMappedProperty(); if (!property.isList()) { Class<?> type = property.getType(); if (property.isCollection() || property.isMap()) { type = property.getComponentType(); } if (!type.isEnum() && !mappedPath.isInverse(0)) { directToType.put(mappedPath.get(0).getUID(), type); } } } } Map<Class<?>, Set<ID>> typeToIds = new HashMap<Class<?>, Set<ID>>(); Set<ID> newHandled = new HashSet<ID>(handled); for (Multimap<UID, STMT> properties : directProps.values()) { for (STMT stmt : properties.values()) { if (stmt.getObject().isResource() && directToType.containsKey(stmt.getPredicate())) { if (!instanceCache.containsKey(stmt.getObject()) && !handled.contains(stmt.getObject())) { Class<?> cl = directToType.get(stmt.getPredicate()); Set<ID> ids = typeToIds.get(cl); if (ids == null) { ids = new HashSet<ID>(); typeToIds.put(cl, ids); } newHandled.add(stmt.getObject().asResource()); ids.add(stmt.getObject().asResource()); } } } } // load for (Map.Entry<Class<?>, Set<ID>> entry : typeToIds.entrySet()) { loadAll(entry.getKey(), entry.getValue(), newHandled); } } @Nullable @SuppressWarnings("unchecked") private <T> Class<? extends T> matchType(Collection<ID> types, Class<T> targetType) { if (types.isEmpty()) { return targetType; } else { Class<? extends T> result = targetType; boolean foundMatch = false; for (ID type : types) { if (type instanceof UID) { UID uid = (UID) type; List<MappedClass> classes = configuration.getMappedClasses(uid); if (classes != null) { for (MappedClass mappedClass : classes) { Class<?> clazz = mappedClass.getJavaClass(); if ((result == null || result.isAssignableFrom(clazz)) && !clazz.isInterface()) { foundMatch = true; result = (Class<? extends T>) clazz; } } } } } if (foundMatch) { return result; } else { return null; } } } private void put(ID resource, Object value) { instanceCache.get(resource).add(value); resourceCache.put(value, resource); } private void recordAddStatement(ID subject, UID predicate, NODE object, UID context) { STMT statement = new STMT(subject, predicate, object, context, true); if (!removedStatements.remove(statement)) { addedStatements.add(statement); } } private void recordRemoveStatement(STMT statement) { if (!addedStatements.remove(statement)) { removedStatements.add(statement); } } private void removeContainer(ID node, UID context) { if (isContainer(node, context)) { for (STMT stmt : findStatements(node, null, null, context, false)) { recordRemoveStatement(stmt); } } } private void removeList(ID node, UID context) { if (findStatements(node, RDF.type, RDF.List, context, true).size() > 0) { removeListInternal(node, context); } } private void removeListInternal(ID node, UID context) { for (STMT statement : findStatements(node, null, null, context, false)) { recordRemoveStatement(statement); NODE object = statement.getObject(); // Remove rdf:rest if (RDF.rest.equals(statement.getPredicate()) && object.isResource()) { removeListInternal((ID) object, context); } } } @Override public ID save(Object instance) { boolean flush = false; if (seen == null) { seen = new HashSet<Object>(); flush = true; } assertMapped(instance); assertHasIdProperty(instance); ID subject = toRDF(instance, null); if (flush) { seen = null; if (flushMode == FlushMode.ALWAYS) { flush(); } } return subject; } @Override public List<ID> saveAll(Object... instances) { List<ID> ids = new ArrayList<ID>(instances.length); seen = new HashSet<Object>(instances.length * 3); for (Object instance : instances) { ids.add(save(assertMapped(instance))); } seen = null; if (flushMode == FlushMode.ALWAYS) { flush(); } return ids; } @Override public void setFlushMode(FlushMode flushMode) { this.flushMode = flushMode; } private <T> void setId(MappedClass mappedClass, ID subject, BeanMap instance) { MappedProperty<?> idProperty = mappedClass.getIdProperty(); if (idProperty != null && !mappedClass.isEnum() && !idProperty.isVirtual()) { Object id = null; Identifier identifier; Class<?> type = idProperty.getType(); IDType idType = idProperty.getIDType(); if (idType == IDType.LOCAL) { identifier = getLID(subject); } else if (idType == IDType.URI) { if (subject.isURI()) { identifier = subject; } else { identifier = null; } } else { identifier = subject; } if (identifier != null) { if (String.class.isAssignableFrom(type)) { String ns = idProperty.getIDNamespace(); if (ns.isEmpty()) { id = identifier.getId(); } else { UID uid = (UID) identifier; if (uid.ns().equals(ns)) { id = uid.ln(); } else { errorHandler.namespaceMismatch(ns, uid.ns()); } } } else if (Identifier.class.isAssignableFrom(type)) { id = identifier; } else { throw new BindException("Cannot assign id of " + mappedClass + " into " + type); } } idProperty.setValue(instance, id); } for (MappedProperty<?> mixinProperty : mappedClass.getMixinProperties()) { Object mixinValue = mixinProperty.getValue(instance); if (mixinValue != null) { MappedClass mixinClass = configuration.getMappedClass(mixinProperty.getTargetType()); // if (mixinClass == null) { // throw new IllegalStateException("Got no mapped class for " + // mixinProperty.getTargetType().getName()); // } setId(mixinClass, subject, new BeanMap(mixinValue)); } } } private BeanMap toBeanMap(Object instance) { return instance instanceof BeanMap ? (BeanMap) instance : new BeanMap(instance); } @SuppressWarnings("unchecked") private void toRDF(Object instance, ID subject, UID parentContext, MappedClass mappedClass, boolean update) { UID uri = mappedClass.getUID(); UID context = parentContext; if (!update && uri != null) { recordAddStatement(subject, RDF.type, uri, context); } BeanMap beanMap = toBeanMap(instance); Multimap<UID, STMT> statements = getProperties(subject, mappedClass, true); for (MappedPath path : mappedClass.getProperties()) { MappedProperty<?> property = path.getMappedProperty(); if (path.isSimpleProperty()) { MappedPredicate mappedPredicate = path.get(0); UID predicate = mappedPredicate.getUID(); if (mappedPredicate.getContext() != null) { context = mappedPredicate.getContext(); } else { context = parentContext; } if (update) { if (statements.containsKey(predicate)) { for (STMT statement : statements.get(predicate)) { if (property.isLocalized() && String.class.equals(property.getType())) { LIT lit = (LIT) statement.getObject(); if (Objects.equal(getCurrentLocale(), lit.getLang())) { recordRemoveStatement(statement); } } else { recordRemoveStatement(statement); NODE object = statement.getObject(); if (object.isResource()) { if (property.isList()) { removeList((ID) object, context); } else if (property.isContainer()) { removeContainer((ID) object, context); } } } } } } Object object = property.getValue(beanMap); if (object != null) { if (property.isArray()) { if (object.getClass().getComponentType().isPrimitive()) { int size = Array.getLength(object); List<Object> list = new ArrayList<Object>(size); for (int i = 0; i < size; i++) { list.add(Array.get(object, i)); } object = list; } else { object = Arrays.asList((Object[]) object); } } if (property.isList()) { ID first = toRDFList((List<?>) object, context); if (first != null) { recordAddStatement(subject, predicate, first, context); } } else if (property.isContainer()) { ID container = toRDFContainer((Collection<?>) object, context, property.getContainerType()); if (container != null) { recordAddStatement(subject, predicate, container, context); } } else if (property.isCollection()) { for (Object o : (Collection<?>) object) { NODE value = toRDFValue(o, context); if (value != null) { recordAddStatement(subject, predicate, value, context); } } } else if (property.isArray()) { // array, but not List or // Container for (Object o : (Collection<?>) object) { NODE value = toRDFValue(o, context); if (value != null) { recordAddStatement(subject, predicate, value, context); } } } else if (property.isLocalized()) { if (property.isMap()) { for (Map.Entry<Locale, String> entry : ((Map<Locale, String>) object).entrySet()) { if (entry.getValue() != null) { LIT literal = new LIT(entry.getValue(), entry.getKey()); recordAddStatement(subject, predicate, literal, context); } } } else { LIT literal = new LIT(object.toString(), getCurrentLocale()); recordAddStatement(subject, predicate, literal, context); } } else if (!property.isMap()) { NODE value = toRDFValue(object, context); if (value != null) { recordAddStatement(subject, predicate, value, context); } } } } else if (property.isMixin()) { Object object = property.getValue(beanMap); if (object != null) { UID subContext = getContext(object, subject, context); toRDF(object, subject, subContext, configuration.getMappedClass(getClass(object)), update); } } } for (MappedProperty<?> property : mappedClass.getDynamicProperties()) { Map<?, ?> properties = (Map) property.getValue(beanMap); if (properties != null) { if (property.getContext() != null) { context = property.getContext(); } else { context = parentContext; } for (Map.Entry<?, ?> entry : properties.entrySet()) { UID predicate = toRDF(entry.getKey(), context).asURI(); if (entry.getValue() instanceof Collection) { for (Object value : ((Collection) entry.getValue())) { NODE object = toRDFValue(value, context); recordAddStatement(subject, predicate, object, context); } } else { NODE object = toRDFValue(entry.getValue(), context); recordAddStatement(subject, predicate, object, context); } } } } } private ID toRDF(Object instance, @Nullable UID parentContext) { if (instance instanceof ID) { return (ID) instance; } BeanMap beanMap = toBeanMap(Assert.notNull(instance, "instance")); Class<?> clazz = getClass(instance); MappedClass mappedClass = configuration.getMappedClass(clazz); ID subject = resourceCache.get(instance); if (subject == null) { subject = getId(mappedClass, beanMap); } if (mappedClass.isEnum()) { subject = new UID(mappedClass.getClassNs(), ((Enum<?>) instance).name()); put(subject, instance); } else if (seen.add(instance)) { UID context = getContext(clazz, subject, parentContext); // Update boolean update = subject != null && exists(subject, mappedClass, context); // Create if (subject == null) { subject = assignId(mappedClass, beanMap); context = getContext(clazz, subject, parentContext); } put(subject, instance); // Build-in namespaces are read-only if (subject.isURI() && configuration.isRestricted((UID) subject)) { return subject; } toRDF(beanMap, subject, context, mappedClass, update); } return subject; } private ID toRDFContainer(Collection<?> collection, UID context, ContainerType containerType) { int i = 0; ID container = connection.createBNode(); recordAddStatement(container, RDF.type, containerType.getUID(), context); for (Object o : collection) { i++; if (o != null) { NODE value = toRDFValue(o, context); recordAddStatement(container, RDF.getContainerMembershipProperty(i), value, context); } } return container; } private ID toRDFList(List<?> list, UID context) { ID firstNode = null; ID currentNode = null; for (Object value : list) { if (currentNode == null) { currentNode = connection.createBNode(); firstNode = currentNode; } else { BID nextNode = connection.createBNode(); recordAddStatement(currentNode, RDF.rest, nextNode, context); currentNode = nextNode; } recordAddStatement(currentNode, RDF.type, RDF.List, context); recordAddStatement(currentNode, RDF.first, toRDFValue(value, context), context); } if (currentNode != null) { recordAddStatement(currentNode, RDF.rest, RDF.nil, context); } return firstNode; } private LIT toRDFLiteral(Object o) { if (o instanceof LIT) { return (LIT) o; } UID dataType = configuration.getConverterRegistry().getDatatype(o.getClass()); return new LIT(configuration.getConverterRegistry().toString(o), dataType); } private NODE toRDFValue(Object o, @Nullable UID context) { if (o instanceof NODE) { return (NODE) o; } Class<?> type = getClass(o); if (configuration.isMapped(type)) { return toRDF(o, context); } else if (o instanceof UID) { return (UID) o; } else { return toRDFLiteral(o); } } }