All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openrdf.repository.object.ObjectConnection Maven / Gradle / Ivy

Go to download

The Object Repository maps Java objects to and from RDF resources and OWL classes to Java classes in a non-intrusive manner that enables developers to work at the object level.

The newest version!
/*
 * Copyright (c) 2007-2009, James Leigh All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * - Neither the name of the openrdf.org nor the names of its contributors may
 *   be used to endorse or promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */
package org.openrdf.repository.object;

import info.aduna.iteration.CloseableIteration;
import info.aduna.iteration.LookAheadIteration;
import org.openrdf.idGenerator.IDGenerator;
import org.openrdf.model.*;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.TupleQuery;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.contextaware.ContextAwareConnection;
import org.openrdf.repository.object.exceptions.BlobConflictException;
import org.openrdf.repository.object.exceptions.BlobStoreException;
import org.openrdf.repository.object.exceptions.ObjectPersistException;
import org.openrdf.repository.object.managers.helpers.WeakValueMap;
import org.openrdf.repository.object.result.ObjectIterator;
import org.openrdf.repository.object.traits.Mergeable;
import org.openrdf.repository.object.traits.RDFObjectBehaviour;
import org.openrdf.repository.object.traits.Refreshable;
import org.openrdf.result.Result;
import org.openrdf.result.impl.ResultImpl;
import org.openrdf.store.blob.BlobObject;
import org.openrdf.store.blob.BlobStore;
import org.openrdf.store.blob.BlobVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.*;

import static org.openrdf.query.QueryLanguage.SPARQL;

/**
 * Primary interface for object retrieval and persistence.
 *
 * @author James Leigh
 *
 */
public class ObjectConnection extends ContextAwareConnection {
	/**
	 * Closes open iterators.
	 *
	 * @param iter
	 */
	public static void close(Iterator iter) {
		ObjectIterator.close(iter);
	}

	final Logger logger = LoggerFactory.getLogger(ObjectConnection.class);
	private final ObjectRepository repository;
	private String language;
	private final TypeManager types;
	private final ObjectFactory of;
	private final Map assigned = new IdentityHashMap();
	private final Set merged = new HashSet();
	private final Map, Map> queries = new HashMap, Map>();
	private final BlobStore blobs;
	private URI versionBundle;
	private BlobVersion blobVersion;
	private final Map cachedObjects = new WeakValueMap(512);

	protected ObjectConnection(ObjectRepository repository,
			RepositoryConnection connection, ObjectFactory factory,
			TypeManager types, BlobStore blobs) throws RepositoryException {
		super(repository, connection);
		this.repository = repository;
		this.of = factory;
		this.types = types;
		this.blobs = blobs;
		types.setConnection(this);
		factory.setObjectConnection(this);
	}

    protected ObjectConnection(ObjectRepository repository,
                               RepositoryConnection connection, ObjectFactory factory,
                               TypeManager types, BlobStore blobs, IDGenerator idGenerator) throws RepositoryException {
        super(repository, connection);
        this.repository = repository;
        this.of = factory;
        this.of.setIdGenerator(idGenerator);
        this.types = types;
        this.blobs = blobs;
        types.setConnection(this);
        factory.setObjectConnection(this);
    }

	@Override
	public ObjectRepository getRepository() {
		return repository;
	}

	/**
	 * An identifier for this connection if assigned, or null.
	 *
	 * @return a {@link URI} representing the current connection or null
	 */
	public URI getVersionBundle() {
		return versionBundle;
	}

	/**
	 * Assigns a URI to this connection to be used for new blob versions and the
	 * default insert graph.
	 *
	 * @param bundle
	 *            a unique URI
	 */
	public void setVersionBundle(URI bundle) {
		versionBundle = bundle;
		if (null == getInsertContext()) {
			setInsertContext(bundle);
		}
	}

	public String toString() {
		URI uri = getVersionBundle();
		if (uri == null)
			return getDelegate().toString();
		return uri.stringValue();
	}

	@Override
	public void close() throws RepositoryException {
		try {
			super.close();
		} finally {
			cachedObjects.clear();
		}
	}

	@Override
	public synchronized void rollback() throws RepositoryException {
		if (blobVersion != null) {
			try {
				blobVersion.rollback();
			} catch (IOException e) {
				throw new RepositoryException(e.toString(), e);
			}
		}
		super.rollback();
		cachedObjects.clear();
	}

	@Override
	public synchronized void commit() throws RepositoryException {
		try {
			try {
				if (blobVersion != null) {
					try {
						blobVersion.prepare();
					} catch(IOException exc) {
						throw new BlobConflictException(exc);
					}
				}
				super.commit();
				if (blobVersion != null) {
					blobVersion.commit();
					blobVersion = null;
				}
			} finally {
				if (blobVersion != null) {
					blobVersion.rollback();
				}
			}
		} catch (IOException e) {
			throw new BlobStoreException(e);
		}
	}

	@Override
	public synchronized void setAutoCommit(boolean auto) throws RepositoryException {
		if (!auto && isAutoCommit()) {
			try {
				try {
					if (blobVersion != null) {
						try {
							blobVersion.prepare();
						} catch(IOException exc) {
							throw new BlobConflictException(exc);
						}
					}
					super.setAutoCommit(auto);
					if (blobVersion != null) {
						blobVersion.commit();
						blobVersion = null;
					}
				} finally {
					if (blobVersion != null) {
						blobVersion.rollback();
						blobVersion = null;
					}
				}
			} catch (IOException e) {
				throw new BlobStoreException(e);
			}
		} else {
			super.setAutoCommit(auto);
		}
	}

	/**
	 * The assign language for this connection, if any.
	 *
	 * @return language tag ("en") or null
	 */
	public String getLanguage() {
		return language;
	}

	/**
	 * Assigns a language to this connection.
	 * @param lang such as "en"
	 */
	public void setLanguage(String lang) {
		this.language = lang;
	}

	/**
	 * Access to the ObjectFactory used with this connection.
	 *
	 * @return ObjectFactory bound to this connection.
	 */
	public ObjectFactory getObjectFactory() {
		return of;
	}

	/**
	 * Imports the instance into the RDF store if not previously imported. If an
	 * object with the same Resource identifier has already been imported into
	 * the store during through this connection, the Resource identifier is
	 * returned and the object is not imported.
	 *
	 * @see #addObject(Resource, Object)
	 * @return the given instance's {@link Resource} identifier or {@link Literal}
	 *         representation
	 */
	public Value addObject(Object instance) throws RepositoryException {
		if (instance instanceof RDFObjectBehaviour) {
			RDFObjectBehaviour support = (RDFObjectBehaviour) instance;
			Object entity = support.getBehaviourDelegate();
			if (entity != instance)
				return addObject(entity);
		}
		if (instance instanceof RDFObject) {
			if (((RDFObject) instance).getObjectConnection() == this)
				return ((RDFObject) instance).getResource();
		} else {
			if (of.isDatatype(instance.getClass()))
				return of.createLiteral(instance);
		}
		Class type = instance.getClass();
		if (RDFObject.class.isAssignableFrom(type) || isEntity(type)) {
			Resource resource = assignResource(instance);
			if (!isAlreadyMerged(resource)) {
				addObject(resource, instance);
			}
			return resource;
		}
		return of.createLiteral(instance);
	}

	/**
	 * Imports the entity into the RDF store using the given URI.
	 */
	public void addObject(String uri, Object entity)
			throws RepositoryException {
		addObject(getValueFactory().createURI(uri), entity);
	}

	/**
	 * Imports the entity into the RDF store using the given handle.
	 */
	public void addObject(Resource resource, Object entity)
			throws RepositoryException {
		if (entity instanceof RDFObjectBehaviour) {
			RDFObjectBehaviour support = (RDFObjectBehaviour) entity;
			Object delegate = support.getBehaviourDelegate();
			if (delegate != entity) {
				addObject(resource, delegate);
				return;
			}
		}
		synchronized (merged) {
			merged.add(resource);
		}
		boolean autoCommit = isAutoCommit();
		if (autoCommit) {
			setAutoCommit(false);
		}
		try {
			Class proxy = entity.getClass();
			Set list = getTypes(proxy, new HashSet(4));
			for (URI type : list) {
				types.addTypeStatement(resource, type);
			}
			Object result = of.createObject(resource, list);
			if (result instanceof Mergeable) {
				((Mergeable) result).merge(entity);
			}
			if (autoCommit) {
				setAutoCommit(true);
			}
			cachedObjects.remove(resource);
		} finally {
			if (autoCommit && !isAutoCommit()) {
				rollback();
				setAutoCommit(true);
			}
		}
	}

	/**
	 * Explicitly adds the concept to the entity.
	 *
	 * @return the entity with new composed concept
	 */
	public  T addDesignation(Object entity, Class concept)
			throws RepositoryException {
		if (entity instanceof RDFObjectBehaviour) {
			RDFObjectBehaviour support = (RDFObjectBehaviour) entity;
			Object delegate = support.getBehaviourDelegate();
			if (delegate != entity) {
				return addDesignation(delegate, concept);
			}
		}
		Resource resource = findResource(entity);
		Set types = new HashSet(4);
		getTypes(entity.getClass(), types);
		addConcept(resource, concept, types);
		RDFObject bean = of.createObject(resource, types);
		assert assertConceptRecorded(bean, concept);
		return (T) cache(bean);
	}

	/**
	 * Explicitly adds the type to the entity.
	 *
	 * @return the entity with new composed types
	 */
	public Object addDesignation(Object entity, String uri) throws RepositoryException {
		return addDesignations(entity, getValueFactory().createURI(uri));
	}

	/**
	 * Explicitly adds the type to the entity.
	 *
	 * @return the entity with new composed type
	 */
	public Object addDesignation(Object entity, URI type)
			throws RepositoryException {
		return addDesignations(entity, type);
	}

	/**
	 * Explicitly adds the types to the entity.
	 *
	 * @return the entity with new composed types
	 */
	public Object addDesignations(Object entity, String... uris) throws RepositoryException {
		URI[] types = new URI[uris.length];
		for (int i=0;i 0;
		Resource resource = findResource(entity);
		Set list = new HashSet(4);
		getTypes(entity.getClass(), list);
		boolean autoCommit = isAutoCommit();
		if (autoCommit) {
			setAutoCommit(false);
		}
		try {
			for (URI type : types) {
				this.types.addTypeStatement(resource, type);
				list.add(type);
			}
			if (autoCommit) {
				setAutoCommit(true);
			}
		} finally {
			if (autoCommit && !isAutoCommit()) {
				rollback();
				setAutoCommit(true);
			}
		}
		return cache(of.createObject(resource, list));
	}

	/**
	 * Explicitly removes the concept from the entity.
	 */
	public void removeDesignation(Object entity, Class concept)
			throws RepositoryException {
		Resource resource = findResource(entity);
		URI type = of.getNameOf(concept);
		if (type == null) {
			throw new ObjectPersistException(
					"Concept is anonymous or is not registered: "
							+ concept.getSimpleName());
		}
		types.removeTypeStatement(resource, type);
		cachedObjects.remove(resource);
	}

	/**
	 * Explicitly removes the type from the entity.
	 */
	public void removeDesignation(Object entity, String uri) throws RepositoryException {
		removeDesignations(entity, getValueFactory().createURI(uri));
	}

	/**
	 * Explicitly removes the type from the entity.
	 */
	public void removeDesignation(Object entity, URI type)
			throws RepositoryException {
		removeDesignations(entity, type);
	}

	/**
	 * Explicitly removes the types from the entity.
	 */
	public void removeDesignations(Object entity, String... uris) throws RepositoryException {
		URI[] types = new URI[uris.length];
		for (int i=0;i 0;
		boolean autoCommit = isAutoCommit();
		if (autoCommit) {
			setAutoCommit(false);
		}
		try {
			Resource resource = findResource(entity);
			for (URI type : types) {
				this.types.removeTypeStatement(resource, type);
			}
			if (autoCommit) {
				setAutoCommit(true);
			}
			cachedObjects.remove(resource);
		} finally {
			if (autoCommit && !isAutoCommit()) {
				rollback();
				setAutoCommit(true);
			}
		}
	}

	/**
	 * Loads a single Object by URI in String form.
	 */
	public Object getObject(String uri) throws RepositoryException {
		assert uri != null;
		return getObject(getValueFactory().createURI(uri));
	}

	/**
	 * Loads a single Object or converts the literal into an Object.
	 */
	public Object getObject(Value value) throws RepositoryException {
		assert value != null;
		if (value instanceof Literal)
			return of.createObject((Literal) value);
		Resource resource = (Resource) value;
		RDFObject cached = cached(resource);
		if (cached != null)
			return cached;
		return cache(of.createObject(resource, types.getTypes(resource)));
	}

	/**
	 * Loads a single Object that is assumed to be of the given concept.
	 */
	public  T getObject(Class concept, String uri)
			throws RepositoryException, QueryEvaluationException {
		assert uri != null;
		return getObject(concept, getValueFactory().createURI(uri));
	}

	/**
	 * Loads a single Object that is assumed to be of the given concept.
	 */
	public  T getObject(Class concept, Resource resource)
			throws RepositoryException, QueryEvaluationException {
		RDFObject cached = cached(resource);
		if (concept.isInstance(cached))
			return concept.cast(cached);
		return getObjects(concept, resource).singleResult();
	}

	/**
	 * Returns a single object that is presumed to have the given rdf:types.
	 */
	public Object getObject(Set types, Resource resource) {
		Class proxy = of.getObjectClass(resource, types);
		RDFObject cached = cached(resource);
		if (cached != null && cached.getClass().equals(proxy))
			return cached;
		return cache(of.createBean(resource, proxy));
	}

	/**
	 * Finds a single object of given concept and uri.
	 */
	public synchronized  T findObject(Class concept, Resource resource) throws RepositoryException, QueryEvaluationException {
		try {
			ObjectQuery query = getObjectQuery(concept, 0);
			query.setBinding("subj", resource);
			return query.evaluate(concept).next();
		} catch (MalformedQueryException e) {
			throw new AssertionError(e);
		}
	}

	/**
	 * Matches objects that have the given concept rdf:type. This method will
	 * include all objects that implement the given concept or a subclass of the
	 * concept. The concept must be a named concept and cannot be mapped to
	 * rdfs:Resource. The result of this method is not guaranteed to be unique
	 * and may continue duplicates. Use the {@link Result#asSet()} method to
	 * ensure uniqueness.
	 *
	 * @see #addDesignation(Object, Class)
	 */
	public synchronized  Result getObjects(Class concept)
			throws RepositoryException,
			QueryEvaluationException {
		try {
			return getObjectQuery(concept, 0).evaluate(concept);
		} catch (MalformedQueryException e) {
			throw new AssertionError(e);
		}
	}

	public  Result getObjects(Class concept, String... uris)
			throws RepositoryException, QueryEvaluationException {
		ValueFactory vf = getValueFactory();
		Resource[] resources = new Resource[uris.length];
		for (int i = 0; i < uris.length; i++) {
			resources[i] = vf.createURI(uris[i]);
		}
		return getObjects(concept, resources);
	}

	/**
	 * Loads the list of resources assumed to implement the given concept. The
	 * concept must be a named concept and cannot be mapped to rdfs:Resource.
	 */
	public synchronized  Result getObjects(final Class concept,
			Resource... resources) throws RepositoryException,
			QueryEvaluationException {
		try {
			int size = resources.length;
			ObjectQuery query = getObjectQuery(concept, size);
			if (size == 1) {
				query.setBinding(ObjectFactory.VAR_PREFIX, resources[0]);
			} else if (size > 1) {
				for (int i = 0; i < size; i++) {
					query.setBinding(ObjectFactory.VAR_PREFIX + i, resources[i]);
				}
			}
			final List list = new ArrayList(size);
			list.addAll(Arrays.asList(resources));
			CloseableIteration iter;
			final Result result = query.evaluate(concept);
			iter = new LookAheadIteration() {
				@Override
				protected T getNextElement() throws QueryEvaluationException {
					T next = result.next();
					if (next != null) {
						list.remove(((RDFObject) next).getResource());
						return next;
					}
					if (!list.isEmpty())
						return (T) cache(of.createObject(list.remove(0)));
					return null;
				}
			};
			return new ResultImpl(iter);
		} catch (MalformedQueryException e) {
			throw new AssertionError(e);
		}
	}

	@SuppressWarnings("unchecked")
	public  T refresh(T object) throws RepositoryException {
		Resource resource = findResource(object);
		if (object instanceof Refreshable) {
			((Refreshable) object).refresh();
		}
		Set types = this.types.getTypes(resource);
		Class proxy = of.getObjectClass(resource, types);
		RDFObject cached = cached(resource);
		if (cached != null && cached != object && cached instanceof Refreshable) {
			((Refreshable) cached).refresh();
		}
		if (cached != null && cached.getClass().equals(proxy))
			return (T) cached;
		return (T) cache(of.createBean(resource, proxy));
	}

	public synchronized BlobObject getBlobObject(final String uri)
			throws RepositoryException {
		if (blobs == null)
			throw new RepositoryException("No configured blob store");
		try {
			if (blobVersion == null && isAutoCommit()) {
				return blobs.open(uri);
			} else if (blobVersion == null) {
				URI version = getVersionBundle();
				if (version == null) {
					blobVersion = blobs.newVersion();
				} else {
					blobVersion = blobs.newVersion(version.stringValue());
				}
				return blobVersion.open(uri);
			} else {
				return blobVersion.open(uri);
			}
		} catch (IOException exc) {
			throw new RepositoryException(exc);
		}
	}

	public BlobObject getBlobObject(URI uri) throws RepositoryException {
		return getBlobObject(uri.stringValue());
	}

	/**
	 * Creates a new query that returns object(s).
	 */
	public ObjectQuery prepareObjectQuery(QueryLanguage ql, String query,
			String baseURI) throws MalformedQueryException, RepositoryException {
		return createObjectQuery(prepareTupleQuery(ql, query, baseURI));
	}

	/**
	 * Creates a new query that returns object(s).
	 */
	public ObjectQuery prepareObjectQuery(QueryLanguage ql, String query)
			throws MalformedQueryException, RepositoryException {
		return createObjectQuery(prepareTupleQuery(ql, query));
	}

	/**
	 * Creates a new query that returns object(s).
	 */
	public ObjectQuery prepareObjectQuery(String query)
			throws MalformedQueryException, RepositoryException {
		return createObjectQuery(prepareTupleQuery(query));
	}

	RDFObject cache(RDFObject object) {
		cachedObjects.put(object.getResource(), object);
		return object;
	}

	RDFObject cached(Resource resource) {
		return cachedObjects.get(resource);
	}

	/** method and result synchronised on this */
	private  ObjectQuery getObjectQuery(Class concept,
			int length) throws MalformedQueryException,
			RepositoryException {
		if (queries.containsKey(concept)
				&& queries.get(concept).containsKey(length)) {
			return queries.get(concept).get(length);
		} else {
			String sparql = of.createObjectQuery(concept, length);
			ObjectQuery query = prepareObjectQuery(SPARQL, sparql);
			Map map = queries.get(concept);
			if (map == null) {
				queries.put(concept, map = new HashMap());
			}
			map.put(length, query);
			return query;
		}
	}

	private ObjectQuery createObjectQuery(TupleQuery query) {
		return new ObjectQuery(this, query);
	}

	private Resource findResource(Object object) {
		if (object instanceof RDFObject)
			return ((RDFObject) object).getResource();
		throw new ObjectPersistException(
				"Object not created by this ObjectFactory: "
						+ object.getClass().getSimpleName());
	}

	private boolean isEntity(Class type) {
		if (type == null)
			return false;
		for (Class face : type.getInterfaces()) {
			if (of.isNamedConcept(face))
				return true;
		}
		if (of.isNamedConcept(type))
			return true;
		return isEntity(type.getSuperclass());
	}

	private boolean assertConceptRecorded(Object bean, Class concept) {
		assert !concept.isInterface()
				|| concept.isAssignableFrom(bean.getClass()) : "Concept is Anonymous or has not bean recorded: "
				+ concept.getSimpleName();
		return true;
	}

	private Resource assignResource(Object bean) {
		synchronized (assigned) {
			if (assigned.containsKey(bean))
				return assigned.get(bean);
			Resource resource = null;
			if (bean instanceof RDFObject) {
				resource = ((RDFObject) bean).getResource();
			}
			if (resource == null) {
				resource = getValueFactory().createBNode();
			}
			assigned.put(bean, resource);
			return resource;
		}
	}

	private boolean isAlreadyMerged(Resource resource) {
		synchronized (merged) {
			return merged.contains(resource);
		}
	}

	private > C getTypes(Class role, C set)
			throws RepositoryException {
		URI type = of.getNameOf(role);
		if (type == null) {
			Class superclass = role.getSuperclass();
			if (superclass != null) {
				getTypes(superclass, set);
			}
			Class[] interfaces = role.getInterfaces();
			for (int i = 0, n = interfaces.length; i < n; i++) {
				getTypes(interfaces[i], set);
			}
		} else {
			set.add(type);
		}
		return set;
	}

	private > C addConcept(Resource resource,
			Class role, C set) throws RepositoryException {
		URI type = of.getNameOf(role);
		if (type == null) {
			throw new ObjectPersistException(
					"Concept is anonymous or is not registered: "
							+ role.getSimpleName());
		}
		types.addTypeStatement(resource, type);
		set.add(type);
		return set;
	}


    public void setIdGenerator(IDGenerator idGenerator) {
        if(this.of != null) {
            of.setIdGenerator(idGenerator);
        }
    }

    public Map getCachedObjects() {
		return cachedObjects;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy