/*
* Copyright (c) 2009 Mysema Ltd.
* All rights reserved.
*
*/
package com.mysema.rdfbean.query;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mysema.commons.lang.CloseableIterator;
import com.mysema.commons.lang.IteratorAdapter;
import com.mysema.query.QueryException;
import com.mysema.query.QueryMetadata;
import com.mysema.query.SearchResults;
import com.mysema.query.Tuple;
import com.mysema.query.support.ProjectableQuery;
import com.mysema.query.support.QueryMixin;
import com.mysema.query.types.EntityPath;
import com.mysema.query.types.Expression;
import com.mysema.query.types.FactoryExpression;
import com.mysema.query.types.FactoryExpressionUtils;
import com.mysema.query.types.QTuple;
import com.mysema.rdfbean.model.BooleanQuery;
import com.mysema.rdfbean.model.ID;
import com.mysema.rdfbean.model.NODE;
import com.mysema.rdfbean.model.RDFConnection;
import com.mysema.rdfbean.model.TupleQuery;
import com.mysema.rdfbean.object.BeanQuery;
import com.mysema.rdfbean.object.Session;
import com.mysema.rdfbean.ontology.Ontology;
import com.mysema.rdfbean.xsd.ConverterRegistry;
/**
* @author tiwe
*/
public class BeanQueryImpl extends ProjectableQuery<BeanQueryImpl> implements
BeanQuery, Closeable {
private static final Logger logger = LoggerFactory.getLogger(BeanQueryImpl.class);
private final Session session;
private final Ontology ontology;
private final ConverterRegistry converterRegistry;
private final RDFConnection connection;
public BeanQueryImpl(Session session, Ontology ontology, RDFConnection connection) {
super(new QueryMixin<BeanQueryImpl>());
queryMixin.setSelf(this);
this.session = session;
this.ontology = ontology;
this.converterRegistry = session.getConfiguration().getConverterRegistry();
this.connection = connection;
}
@Override
public void close() throws IOException {
// ?!?
}
@Override
public long count() {
TupleQuery query = createTupleQuery(true);
if (!connection.getQueryOptions().isCountViaAggregation()) {
long counter = 0;
CloseableIterator<Map<String, NODE>> tuples = query.getTuples();
try {
while (tuples.hasNext()) {
counter++;
tuples.next();
}
} finally {
tuples.close();
}
return counter;
} else {
List<Map<String, NODE>> results = IteratorAdapter.asList(query.getTuples());
NODE result = results.get(0).values().iterator().next();
if (result.isLiteral()) {
return Long.valueOf(result.getValue());
} else {
throw new IllegalArgumentException(result.toString());
}
}
}
private RDFQueryBuilder createBuilder() {
return new RDFQueryBuilder(
connection,
session,
session.getConfiguration(),
ontology,
queryMixin.getMetadata());
}
private BooleanQuery createBooleanQuery() {
return createBuilder().createBooleanQuery();
}
private TupleQuery createTupleQuery(boolean forCount) {
return createBuilder().createTupleQuery(forCount);
}
@Override
public boolean exists() {
return createBooleanQuery().getBoolean();
}
@Override
public BeanQuery from(EntityPath<?>... o) {
return queryMixin.from(o);
}
@SuppressWarnings("unchecked")
@Nullable
private <RT> RT getAsProjectionValue(Expression<RT> expr,
Map<String, NODE> nodes, List<String> variables, AtomicInteger offset) {
if (expr instanceof FactoryExpression<?>) {
FactoryExpression<?> factoryExpr = (FactoryExpression<?>) expr;
Object[] args = new Object[factoryExpr.getArgs().size()];
for (int i = 0; i < args.length; i++) {
NODE node = nodes.get(variables.get(offset.intValue() + i));
args[i] = getAsProjectionValue(node, factoryExpr.getArgs().get(i).getType());
}
offset.addAndGet(args.length);
// offset.add(args.length);
try {
return (RT) factoryExpr.newInstance(args);
} catch (Exception e) {
throw new QueryException(e.getMessage(), e);
}
} else {
NODE node = nodes.get(variables.get(offset.intValue()));
offset.addAndGet(1);
// offset.add(1);
if (node != null) {
return getAsProjectionValue(node, expr.getType());
} else {
return null;
}
}
}
@SuppressWarnings("unchecked")
private <RT> RT getAsProjectionValue(NODE node, Class<RT> type) {
if (node.isResource()) {
if (type.equals(String.class)) {
// TODO : always return LID ?
return (RT) session.getLID(node.asResource()).getId();
} else {
return session.get(type, node.asResource());
}
} else {
return converterRegistry.fromString(node.getValue(), type);
}
}
@Override
public CloseableIterator<Tuple> iterate(final Expression<?>... args) {
queryMixin.addProjection(args);
final QTuple qTuple = new QTuple(args);
final TupleQuery query = createTupleQuery(false);
final CloseableIterator<Map<String, NODE>> results = query.getTuples();
return new CloseableIterator<Tuple>() {
@Override
public void close() {
results.close();
}
@Override
public boolean hasNext() {
return results.hasNext();
}
@Override
public Tuple next() {
Map<String, NODE> row = results.next();
Object[] rv = new Object[args.length];
AtomicInteger offset = new AtomicInteger();
for (int i = 0; i < rv.length; i++) {
rv[i] = getAsProjectionValue(args[i], row, query.getVariables(), offset);
}
return qTuple.newInstance(rv);
}
@Override
public void remove() {
results.remove();
}
};
}
@SuppressWarnings("unchecked")
@Override
public <RT> List<RT> list(Expression<RT> projection) {
if (!converterRegistry.supports(projection.getType())
&& !(projection instanceof FactoryExpression<?>)) {
// bulk load of resources
queryMixin.addProjection(projection);
long start = System.currentTimeMillis();
TupleQuery query = createTupleQuery(false);
CloseableIterator<Map<String, NODE>> results = query.getTuples();
List<ID> ids = new ArrayList<ID>();
try {
while (results.hasNext()) {
Map<String, NODE> row = results.next();
if (!row.isEmpty()) {
ids.add(row.values().iterator().next().asResource());
} else {
ids.add(null);
}
}
} finally {
results.close();
}
long duration = System.currentTimeMillis() - start;
if (logger.isWarnEnabled() && duration > 500) {
logger.warn("list ids of " + projection + " took " + duration + "ms");
}
List<RT> rv = (List) session.getAll(projection.getType(), ids.toArray(new ID[ids.size()]));
duration = System.currentTimeMillis() - start;
if (logger.isWarnEnabled() && duration > 500) {
logger.warn("list of " + projection + " took " + duration + "ms");
}
return rv;
} else {
return super.list(projection);
}
}
@Override
public <RT> CloseableIterator<RT> iterate(Expression<RT> p) {
final Expression<RT> projection = normalize(p);
queryMixin.addProjection(projection);
final TupleQuery query = createTupleQuery(false);
final CloseableIterator<Map<String, NODE>> results = query.getTuples();
return new CloseableIterator<RT>() {
@Override
public void close() {
results.close();
}
@Override
public boolean hasNext() {
return results.hasNext();
}
@Override
public RT next() {
Map<String, NODE> row = results.next();
return getAsProjectionValue(projection, row, query
.getVariables(), new AtomicInteger());
}
@Override
public void remove() {
results.remove();
}
};
}
@Override
public <RT> SearchResults<RT> listResults(Expression<RT> p) {
Expression<RT> projection = normalize(p);
queryMixin.addProjection(projection);
long total = count();
QueryMetadata md = queryMixin.getMetadata();
md.clearProjection();
List<RT> results = list(projection);
return new SearchResults<RT>(results,
md.getModifiers().getLimit(),
md.getModifiers().getOffset(),
total);
}
@Override
public SearchResults<Tuple> listResults(Expression<?>... args) {
return listResults(new QTuple(args));
}
@SuppressWarnings("unchecked")
private <T> Expression<T> normalize(Expression<T> expr) {
if (expr instanceof FactoryExpression<?>) {
return (Expression<T>) FactoryExpressionUtils.wrap((FactoryExpression<?>) expr);
} else {
return expr;
}
}
@Override
public Tuple uniqueResult(Expression<?>... args) {
queryMixin.setUnique(true);
if (queryMixin.getMetadata().getModifiers().getLimit() == null) {
limit(2l);
}
return uniqueResult(iterate(args));
}
@Override
public <RT> RT uniqueResult(Expression<RT> expr) {
queryMixin.setUnique(true);
if (queryMixin.getMetadata().getModifiers().getLimit() == null) {
limit(2l);
}
return uniqueResult(iterate(expr));
}
}