/*
* Copyright (c) 2014 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:
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.common.instance.orient.storage;
import java.util.Iterator;
import javax.xml.namespace.QName;
import org.apache.commons.codec.DecoderException;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery;
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.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;
/**
* OrientDB instance collection based on a SQL query.
*
* @author Simon Templer
*/
public class SQLQueryInstanceCollection implements InstanceCollection {
private class QueryIterator implements ResourceIterator<Instance> {
private DatabaseReference<ODatabaseDocumentTx> ref;
private DatabaseHandle handle;
private Iterator<ODocument> iterator;
private final int limit;
public QueryIterator(int limit) {
super();
this.limit = limit;
}
@Override
public boolean hasNext() {
update();
return iterator.hasNext();
}
@SuppressWarnings("unchecked")
private void update() {
if (ref == null) {
ref = database.openRead();
handle = new DatabaseHandle(ref.getDatabase());
/*
* FIXME use asynchronous query instead, to be able to provide
* instances iteratively instead of retrieving all at once (as
* the query below actually yields a list)
*/
iterator = (Iterator<ODocument>) ref.getDatabase()
.query(new OSQLSynchQuery<ODocument>(sqlQuery, limit)).iterator();
}
// make sure the database is associated to the current thread
ODatabaseRecordThreadLocal.INSTANCE.set(ref.getDatabase());
}
@Override
public Instance next() {
if (hasNext()) {
ODocument doc = iterator.next();
try {
// find associated type
QName typeName = ONamespaceMap.decode(doc.getSchemaClass().getName());
TypeDefinition type = types.getType(typeName);
// TODO react in case it is not found?
Instance instance = new OInstance(doc, type, ref.getDatabase(), dataSet);
return handle.addInstance(instance);
} catch (DecoderException e) {
throw new IllegalStateException("Failed to decode instance type", e);
}
}
else {
throw new IllegalStateException(
"No more instances available, you should have checked hasNext().");
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@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 String sqlQuery;
private final TypeIndex types;
private final DataSet dataSet;
/**
* Create a new instance collection based on the given SQL query.
*
* @param database the database to query
* @param sqlQuery the SQL query string (make sure type names are properly
* encoded using {@link ONamespaceMap})
* @param types the type index where type definitions to associate are
* retrieved from
* @param dataSet the data set to associated to the instances
*/
public SQLQueryInstanceCollection(LocalOrientDB database, String sqlQuery, TypeIndex types,
DataSet dataSet) {
this.database = database;
this.sqlQuery = sqlQuery;
this.types = types;
this.dataSet = dataSet;
}
@Override
public InstanceCollection select(Filter filter) {
return FilteredInstanceCollection.applyFilter(this, filter);
}
@Override
public InstanceReference getReference(Instance instance) {
return OrientInstanceReference.createReference(instance);
}
@Override
public Instance getInstance(InstanceReference reference) {
OrientInstanceReference ref = (OrientInstanceReference) reference;
return ref.load(database, this);
}
@Override
public ResourceIterator<Instance> iterator() {
return iterator(-1);
}
private ResourceIterator<Instance> iterator(int limit) {
return new QueryIterator(limit);
}
@Override
public boolean hasSize() {
return false;
}
@Override
public int size() {
return UNKNOWN_SIZE;
}
@Override
public boolean isEmpty() {
try (ResourceIterator<Instance> it = iterator(1)) {
return !it.hasNext();
}
}
}