/*
* 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.advisers.helpers;
import static java.util.Collections.EMPTY_LIST;
import info.aduna.iteration.CloseableIteration;
import info.aduna.iteration.CloseableIteratorIteration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.openrdf.model.Resource;
import org.openrdf.model.Value;
import org.openrdf.query.BindingSet;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.object.ObjectConnection;
import org.openrdf.repository.object.ObjectQuery;
import org.openrdf.repository.object.RDFObject;
import org.openrdf.repository.object.exceptions.ObjectPersistException;
import org.openrdf.repository.object.result.ObjectCursor;
import org.openrdf.repository.object.result.ObjectIterator;
import org.openrdf.repository.object.traits.ManagedRDFObject;
import org.openrdf.repository.object.traits.PropertyConsumer;
/**
* A set for a given getResource(), predicate.
*
* @author James Leigh
*/
public class CachedPropertySet extends RemotePropertySet implements
PropertyConsumer {
private static final int CACHE_LIMIT = 10;
List<Object> cache;
boolean cached;
private ObjectQueryFactory factory;
private PropertySetFactory creator;
private String binding;
private List<BindingSet> bindings;
private boolean merged;
public CachedPropertySet(ManagedRDFObject bean, PropertySetModifier property) {
super(bean, property);
this.factory = bean.getObjectQueryFactory();
}
public void setPropertySetFactory(PropertySetFactory creator) {
this.creator = creator;
}
public synchronized void usePropertyBindings(String binding, List<BindingSet> bindings) {
this.binding = binding;
this.bindings = bindings;
}
@Override
public synchronized void refresh() {
super.refresh();
cached = false;
cache = null;
binding = null;
bindings = null;
}
@Override
public void clear() {
if (isCacheComplete() && !cache.isEmpty()) {
ObjectConnection conn = getObjectConnection();
try {
boolean autoCommit = conn.isAutoCommit();
if (autoCommit)
conn.setAutoCommit(false);
try {
for (Object o : cache)
remove(o);
if (autoCommit)
conn.setAutoCommit(true);
} finally {
if (autoCommit && !conn.isAutoCommit()) {
conn.rollback();
conn.setAutoCommit(true);
}
}
} catch (RepositoryException e) {
throw new ObjectPersistException(e);
}
refreshCache();
} else if (!cached || !cache.isEmpty()) {
super.clear();
refreshCache();
}
cache = Collections.EMPTY_LIST;
cached = true;
}
@Override
public void setSingle(Object o) {
if (!cached || !cache.isEmpty()) {
super.setSingle(o);
} else if (o != null) {
add(o);
}
if (!merged) {
cache = o == null ? EMPTY_LIST : Collections.singletonList(o);
cached = true;
}
}
@Override
public void setAll(Set<?> set) {
if (!cached || !cache.isEmpty()) {
super.setAll(set);
} else if (!set.isEmpty()) {
addAll(set);
}
if (!merged) {
cache = set == null ? EMPTY_LIST : new ArrayList<Object>(set);
cached = true;
}
}
@Override
public boolean contains(Object o) {
if (isCacheComplete())
return cache.contains(o);
if (cached && cache.contains(o))
return true;
return super.contains(o);
}
@Override
public boolean containsAll(Collection<?> c) {
if (isCacheComplete())
return cache.containsAll(c);
if (cached && cache.containsAll(c))
return true;
return super.containsAll(c);
}
@Override
public Object getSingle() {
if (cached && cache.isEmpty())
return null;
if (cached)
return cache.get(0);
return super.getSingle();
}
@Override
public boolean isEmpty() {
if (cached)
return cache.isEmpty();
return super.isEmpty();
}
@Override
public boolean removeAll(Collection<?> c) {
refreshCache();
return super.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
refreshCache();
return super.retainAll(c);
}
@Override
public int size() {
if (isCacheComplete())
return cache.size();
return super.size();
}
@Override
public Iterator<Object> iterator() {
if (isCacheComplete()) {
final Iterator<Object> iter = cache.iterator();
return new Iterator<Object>() {
private Object e;
public boolean hasNext() {
return iter.hasNext();
}
public Object next() {
return e = iter.next();
}
public void remove() {
CachedPropertySet.this.remove(e);
}
};
}
return super.iterator();
}
@Override
public Object[] toArray() {
if (isCacheComplete())
return cache.toArray();
return super.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
if (isCacheComplete())
return cache.toArray(a);
return super.toArray(a);
}
protected void refreshCache() {
if (cached) {
for (Object e : cache) {
refresh(e);
}
}
}
private boolean isCacheComplete() {
return cached && cache.size() < CACHE_LIMIT;
}
@Override
protected Value getValue(Object instance) throws RepositoryException {
Value value = super.getValue(instance);
if (!merged && value instanceof Resource && !isManaged(instance)) {
merged = true;
}
return value;
}
private boolean isManaged(Object instance) {
return instance instanceof RDFObject
&& ((RDFObject) instance).getObjectConnection() == getObjectConnection();
}
@Override
protected synchronized CloseableIteration<?, ?> getObjects() throws RepositoryException,
QueryEvaluationException {
if (creator == null || factory == null) {
return super.getObjects();
} else if (binding == null) {
ObjectQuery query = factory.createQuery(creator);
if (query == null)
return super.getObjects();
try {
query.setBinding("self", getResource());
return query.evaluate(creator.getPropertyType());
} finally {
factory.returnQuery(creator, query);
}
} else {
CloseableIteratorIteration<BindingSet, QueryEvaluationException> result;
result = new CloseableIteratorIteration<BindingSet, QueryEvaluationException>(
bindings.iterator());
return new ObjectCursor(getObjectConnection(), result, binding);
}
}
@Override
protected ObjectIterator<?, Object> getObjectIterator() {
try {
return new ObjectIterator<Object, Object>(getObjects()) {
private List<Object> list = new ArrayList<Object>(CACHE_LIMIT);
@Override
protected Object convert(Object instance)
throws RepositoryException {
if (list != null && list.size() < CACHE_LIMIT)
list.add(instance);
return instance;
}
@Override
protected void remove(Object instance) {
list = null;
CachedPropertySet.this.remove(instance);
}
@Override
public void close() {
try {
if (list != null
&& (!hasNext() || list.size() == CACHE_LIMIT)) {
cache = list;
cached = true;
}
} finally {
super.close();
}
}
};
} catch (RepositoryException e) {
throw new ObjectPersistException(e);
} catch (QueryEvaluationException e) {
throw new ObjectPersistException(e);
}
}
}