/*
* Copyright (c) 2012 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* HUMBOLDT EU Integrated Project #030962
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.common.instance.orient.storage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import javax.xml.namespace.QName;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;
import com.orientechnologies.orient.core.iterator.ORecordIteratorClass;
import com.orientechnologies.orient.core.record.impl.ODocument;
import eu.esdihumboldt.hale.common.instance.model.DataSet;
import eu.esdihumboldt.hale.common.instance.model.Filter;
import eu.esdihumboldt.hale.common.instance.model.Instance;
import eu.esdihumboldt.hale.common.instance.model.InstanceCollection;
import eu.esdihumboldt.hale.common.instance.model.InstanceReference;
import eu.esdihumboldt.hale.common.instance.model.InstanceResolver;
import eu.esdihumboldt.hale.common.instance.model.MetaFilter;
import eu.esdihumboldt.hale.common.instance.model.ResourceIterator;
import eu.esdihumboldt.hale.common.instance.model.impl.FilteredInstanceCollection;
import eu.esdihumboldt.hale.common.instance.orient.OInstance;
import eu.esdihumboldt.hale.common.instance.orient.internal.ONamespaceMap;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
import eu.esdihumboldt.hale.common.schema.model.TypeIndex;
/**
* Instance collection based on a {@link LocalOrientDB}
*
* FIXME implement instance collection fan-out (InstanceCollection2)
*
* @author Simon Templer
*/
public class BrowseOrientInstanceCollection implements InstanceCollection {
private class OrientInstanceIterator implements ResourceIterator<Instance> {
private DatabaseReference<ODatabaseDocumentTx> ref;
private Map<String, TypeDefinition> classTypes;
private Queue<String> classQueue;
private String currentClass;
private ORecordIteratorClass<ODocument> currentIterator;
private DatabaseHandle handle;
private boolean allowUpdate = true;
/**
* @see Iterator#hasNext()
*/
@Override
public boolean hasNext() {
update();
if (currentClass == null || currentIterator == null) {
return false;
}
return true;
// return currentIterator.hasNext(); XXX Bug in OrientDB 1.0rc8: hasNext will move to the next element
}
private void update() {
if (ref == null) {
// initialize the connection and the state
classQueue = new LinkedList<String>();
classTypes = new HashMap<String, TypeDefinition>();
for (TypeDefinition type : types.getMappingRelevantTypes()) {
String className = ONamespaceMap.encode(type.getName());
// ONameUtil.encodeName(type.getIdentifier());
classTypes.put(className, type);
classQueue.add(className);
}
ref = database.openRead();
handle = new DatabaseHandle(ref.getDatabase());
if (!classQueue.isEmpty()) {
currentClass = classQueue.poll();
if (ref.getDatabase().getMetadata().getSchema().getClass(currentClass) != null
&& ref.getDatabase().countClass(currentClass) > 0) {
// XXX set a fetch plan?
currentIterator = ref.getDatabase().browseClass(currentClass);
}
else {
currentIterator = null;
}
}
}
// make sure the database is associated to the current thread
ODatabaseRecordThreadLocal.INSTANCE.set(ref.getDatabase());
if (!allowUpdate) {
return;
}
else {
allowUpdate = false; // ensure that hasNext is only called once
// per next on the current iterator (due
// to the OrientDB bug)
}
// update class if needed
while (currentClass != null
&& (currentIterator == null || !currentIterator.hasNext())) {
currentClass = classQueue.poll();
if (ref.getDatabase().getMetadata().getSchema().getClass(currentClass) != null
&& ref.getDatabase().countClass(currentClass) > 0) {
// XXX set a fetch plan?
currentIterator = ref.getDatabase().browseClass(currentClass);
currentIterator.setFetchPlan("*:0");
}
else {
currentIterator = null;
}
}
}
/**
* @see Iterator#next()
*/
@Override
public Instance next() {
if (hasNext()) {
ODocument doc = currentIterator.next();
allowUpdate = true; // allow updating in hasNext
Instance instance = new OInstance(doc, getCurrentType(), ref.getDatabase(),
dataSet);
return handle.addInstance(instance);
}
else {
throw new IllegalStateException(
"No more instances available, you should have checked hasNext().");
}
}
private TypeDefinition getCurrentType() {
return classTypes.get(currentClass);
}
/**
* @see Iterator#remove()
*/
@Override
public void remove() {
throw new UnsupportedOperationException();
}
/**
* @see ResourceIterator#close()
*/
@Override
public void close() {
if (ref != null) {
// connection is closed in DatabaseHandle
ref.dispose(false);
// try closing the database handle (e.g. if no objects were
// added)
handle.tryClose();
}
}
}
private final LocalOrientDB database;
private final TypeIndex types;
private final DataSet dataSet;
/**
* Create an instance collection based on the given database
*
* @param database the database
* @param types the type index
* @param dataSet the data set the instances are associated to
*/
public BrowseOrientInstanceCollection(LocalOrientDB database, TypeIndex types,
DataSet dataSet) {
super();
this.database = database;
this.types = types;
this.dataSet = dataSet;
}
/**
* @see InstanceCollection#iterator()
*/
@Override
public ResourceIterator<Instance> iterator() {
return new OrientInstanceIterator();
}
/**
* @see InstanceCollection#hasSize()
*/
@Override
public boolean hasSize() {
return true;
}
/**
* @see InstanceCollection#size()
*/
@Override
public int size() {
int size = 0;
DatabaseReference<ODatabaseDocumentTx> ref = database.openRead();
ODatabaseDocumentTx db = ref.getDatabase();
try {
Collection<String> classes = getMainClassNames();
for (String clazz : classes) {
try {
size += db.countClass(clazz);
} catch (IllegalArgumentException e) {
// class not contained in the database
}
}
} finally {
ref.dispose();
}
return size;
}
/**
* Get the main class names
*
* @return the main class names
*/
private Collection<String> getMainClassNames() {
Collection<String> classes = new ArrayList<String>();
for (TypeDefinition type : types.getMappingRelevantTypes()) {
classes.add(ONamespaceMap.encode(type.getName()));
// ONameUtil.encodeName(type.getIdentifier()));
}
return classes;
}
/**
* @see InstanceCollection#isEmpty()
*/
@Override
public boolean isEmpty() {
DatabaseReference<ODatabaseDocumentTx> ref = database.openRead();
ODatabaseDocumentTx db = ref.getDatabase();
// make sure the database is associated to the current thread XXX not
// sure if this is necessary
ODatabaseRecordThreadLocal.INSTANCE.set(db);
try {
Collection<String> classes = getMainClassNames();
for (String clazz : classes) {
try {
if (db.countClass(clazz) > 0) {
return false;
}
} catch (IllegalArgumentException e) {
// ignore
}
}
return true;
} finally {
ref.dispose();
}
}
/**
* @see InstanceCollection#select(Filter)
*/
@Override
public InstanceCollection select(Filter filter) {
// XXX special handling for MetaFilter (as SQL query)
if (filter instanceof MetaFilter) {
MetaFilter metaFilter = (MetaFilter) filter;
if (metaFilter.getType() != null && !metaFilter.getValues().isEmpty()) {
StringBuilder query = new StringBuilder();
// build SQL query (XXX not injection safe)
query.append("SELECT FROM ");
query.append(ONamespaceMap.encode(metaFilter.getType().getName()));
query.append(" WHERE ");
query.append(OInstance.FIELD_METADATA);
query.append(".");
query.append(ONamespaceMap.encode(new QName(metaFilter.getMetadataKey())));
query.append(" in [");
boolean first = true;
for (Object value : metaFilter.getValues()) {
if (first) {
first = false;
}
else {
query.append(',');
}
query.append('\'');
query.append(value);
query.append('\'');
}
query.append("]");
return new SQLQueryInstanceCollection(database, query.toString(), types, dataSet);
}
}
// TODO specific support for Type filters?!
return FilteredInstanceCollection.applyFilter(this, filter);
}
/**
* @see InstanceResolver#getReference(Instance)
*/
@Override
public InstanceReference getReference(Instance instance) {
return OrientInstanceReference.createReference(instance);
}
/**
* @see InstanceResolver#getInstance(InstanceReference)
*/
@Override
public Instance getInstance(InstanceReference reference) {
OrientInstanceReference ref = (OrientInstanceReference) reference;
return ref.load(database, this);
}
}