org.topazproject.otm.stores.ItqlStore.java Source code

Java tutorial

Introduction

Here is the source code for org.topazproject.otm.stores.ItqlStore.java

Source

/* $HeadURL::                                                                            $
 * $Id$
 *
 * Copyright (c) 2007-2010 by Topaz, Inc.
 * http://topazproject.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.topazproject.otm.stores;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.topazproject.mulgara.itql.Answer;
import org.topazproject.mulgara.itql.AnswerException;
import org.topazproject.mulgara.itql.ItqlClient;
import org.topazproject.mulgara.itql.ItqlClientFactory;
import org.topazproject.mulgara.itql.DefaultItqlClientFactory;
import org.topazproject.otm.AbstractConnection;
import org.topazproject.otm.Blob;
import org.topazproject.otm.ClassMetadata;
import org.topazproject.otm.CollectionType;
import org.topazproject.otm.Connection;
import org.topazproject.otm.Criteria;
import org.topazproject.otm.EntityMode;
import org.topazproject.otm.Filter;
import org.topazproject.otm.GraphConfig;
import org.topazproject.otm.OtmException;
import org.topazproject.otm.Rdf;
import org.topazproject.otm.RdfUtil;
import org.topazproject.otm.Session;
import org.topazproject.otm.SessionFactory;
import org.topazproject.otm.SearchStore;
import org.topazproject.otm.TripleStore;
import org.topazproject.otm.criterion.itql.ComparisonCriterionBuilder;
import org.topazproject.otm.filter.AbstractFilterImpl;
import org.topazproject.otm.mapping.AbstractMapper;
import org.topazproject.otm.mapping.PropertyBinder;
import org.topazproject.otm.mapping.Mapper;
import org.topazproject.otm.mapping.RdfMapper;
import org.topazproject.otm.mapping.java.AbstractFieldBinder;
import org.topazproject.otm.metadata.Definition;
import org.topazproject.otm.metadata.PropertyDefinition;
import org.topazproject.otm.metadata.RdfDefinition;
import org.topazproject.otm.metadata.SearchableDefinition;
import org.topazproject.otm.query.GenericQuery;
import org.topazproject.otm.query.QueryException;
import org.topazproject.otm.query.QueryInfo;
import org.topazproject.otm.query.Results;
import org.topazproject.util.functional.Pair;

/**
 * An iTQL based store.
 *
 * @author Ronald Tschalr
 */
public class ItqlStore extends AbstractTripleStore implements SearchStore {
    private static final Log log = LogFactory.getLog(ItqlStore.class);
    private final List<ItqlClient> conCache = new ArrayList<ItqlClient>();
    private final ItqlClientFactory itqlFactory;
    private final URI serverUri;

    // temporary hack for problems with mulgara's blank-nodes (must be unique for whole tx)
    private int bnCtr = 0;

    /**
     * Create a new itql-store instance.
     * The server URI will take one of two forms:
     * <ul>
     *   <li>rmi://hostname/server</li>
     *   <li>local:///server</li>
     * </ul>
     * The RMI scheme is used to access an external TQL store.
     * The <code>hostname</code> refers to the name of the computer that the
     * store is running on. The <code>server</code> is the name of the server
     * that TQL store is providing. By default, Mulgara servers are run with a
     * name of "server1". So to access a server on the local machine, the URI
     * might be: <code>rmi://localhost/server1</code>
     *
     * The <code>LOCAL</code> scheme is used to access an embedded Mulgara
     * database in the current JVM. The host name is <code>null</code> which
     * leads to the root of the path coming immediately after the "//"
     * characters. The server name is defined here in order to create the
     * database, so it can be anything.
     *
     * Graphs created on this store should use the server URI with an added
     * fragment. For instance:
     * <ul>
     *   <li>rmi://localhost/server1#data</li>
     *   <li>local:///myserver#data</li>
     * </ul>
     *
     * @param server  the uri of the iTQL server.
     * @throws OtmException OTM Exception
     */
    public ItqlStore(URI server) throws OtmException {
        this(server, new DefaultItqlClientFactory());
    }

    /**
     * Create a new itql-store instance.
     *
     * @param server  the uri of the iTQL server.
     * @param icf     the itql-client-factory to use
     * @throws OtmException OTM Exception
     */
    public ItqlStore(URI server, ItqlClientFactory icf) throws OtmException {
        serverUri = server;
        itqlFactory = icf;

        //XXX: configure these
        ComparisonCriterionBuilder cc = new ComparisonCriterionBuilder();
        critBuilders.put("gt", cc);
        critBuilders.put("ge", cc);
        critBuilders.put("lt", cc);
        critBuilders.put("le", cc);
    }

    public Connection openConnection(Session sess, boolean readOnly) throws OtmException {
        return new ItqlStoreConnection(sess, readOnly);
    }

    public <T> void insert(ClassMetadata cm, Collection<RdfMapper> fields, String id, T o, Connection con)
            throws OtmException {
        insertInternal(cm, fields, id, o, con);
    }

    public <T> void index(ClassMetadata cm, Collection<SearchableDefinition> fields, String id, T o, Connection con)
            throws OtmException {
        Collection<Mapper> mappers = new ArrayList<Mapper>();

        for (SearchableDefinition sd : fields) {
            final Map<EntityMode, PropertyBinder> propertyBinders = new HashMap<EntityMode, PropertyBinder>();
            final SearchableDefinition def = sd;

            Mapper m = cm.getMapperByName(sd.getLocalName());
            if (m instanceof RdfMapper) {
                propertyBinders.putAll(m.getBinders());
            } else {
                Session sess = ((ItqlStoreConnection) con).getSession();
                propertyBinders.put(sess.getEntityMode(), getBlobBinder(cm, sess, id));
            }

            m = new AbstractMapper(propertyBinders) {
                public PropertyDefinition getDefinition() {
                    return def;
                }
            };
            mappers.add(m);
        }

        insertInternal(cm, mappers, id, o, con);
    }

    private static PropertyBinder getBlobBinder(final ClassMetadata cm, final Session sess, final String id) {
        final PropertyBinder origBinder = cm.getBlobField().getBinder(sess);
        if (origBinder.getSerializer() != null)
            return origBinder;

        // FIXME: this has too many assumptions...
        return new AbstractFieldBinder(null, null, null) {
            public List get(Object o) throws OtmException {

                Blob blob = sess.getSessionFactory().getBlobStore().getBlob(cm, id, o, sess.getBlobStoreCon());
                byte[] b = blob.exists() ? blob.readAll() : null;

                if ((b == null) || (b.length == 0))
                    return Collections.emptyList();

                try {
                    return Collections.singletonList(new String(b, "UTF-8"));
                } catch (UnsupportedEncodingException uee) {
                    throw new OtmException("Unexpected error", uee);
                }
            }

            public void set(Object o, List l) {
            }
        };
    }

    private <T, M extends Mapper> void insertInternal(ClassMetadata cm, Collection<M> fields, String id, T o,
            Connection con) throws OtmException {
        ItqlStoreConnection isc = (ItqlStoreConnection) con;

        Map<String, List<M>> mappersByGraph = groupMappersByGraph(cm, fields);

        // for every graph create an insert statement
        for (Map.Entry<String, List<M>> stringListEntry : mappersByGraph.entrySet()) {
            StringBuilder insert = isc.getInserts().get(stringListEntry.getKey());
            if (insert == null)
                isc.getInserts().put(stringListEntry.getKey(), insert = new StringBuilder(500));

            if (stringListEntry.getKey().equals(cm.getGraph())) {
                for (String type : cm.getAllTypes())
                    addStmt(insert, id, Rdf.rdf + "type", type, null, true);
            }

            buildInsert(insert, stringListEntry.getValue(), id, o, isc.getSession());
        }
    }

    public void flush(Connection con) throws OtmException {
        ItqlStoreConnection isc = (ItqlStoreConnection) con;
        StringBuilder insert = new StringBuilder(500);

        for (String g : isc.getInserts().keySet()) {
            StringBuilder stmts = isc.getInserts().get(g);
            if (stmts.length() == 0)
                continue;

            insert.append("insert ").append(stmts).append("into <").append(getGraphUri(g, isc)).append(">;");
        }

        isc.getInserts().clear();

        if (log.isDebugEnabled())
            log.debug("flush: " + insert);

        if (insert.length() == 0)
            return;

        try {
            isc.getItqlClient().doUpdate(insert.toString());
        } catch (IOException ioe) {
            throw new OtmException("error performing flush", ioe);
        }
    }

    private static <T> Map<String, List<T>> groupMappersByGraph(ClassMetadata cm, Collection<T> fields) {
        Map<String, List<T>> mappersByGraph = new HashMap<String, List<T>>();

        if (cm.getAllTypes().size() > 0)
            mappersByGraph.put(cm.getGraph(), new ArrayList<T>());

        for (T p : fields) {
            String g = getGraph((p instanceof Definition) ? (Definition) p : ((Mapper) p).getDefinition(), cm);
            List<T> pList = mappersByGraph.get(g);
            if (pList == null)
                mappersByGraph.put(g, pList = new ArrayList<T>());
            pList.add(p);
        }

        return mappersByGraph;
    }

    private static String getGraph(Definition def, ClassMetadata cm) {
        if (def instanceof RdfDefinition) {
            RdfDefinition rd = (RdfDefinition) def;
            return (rd.getGraph() != null) ? rd.getGraph() : cm.getGraph();
        }

        if (def instanceof SearchableDefinition) {
            return ((SearchableDefinition) def).getIndex();
        }

        throw new Error("Unexpected definition type: " + def.getClass() + ": " + def);
    }

    @SuppressWarnings("unchecked")
    private void buildInsert(StringBuilder buf, List<? extends Mapper> pList, String id, Object o, Session sess)
            throws OtmException {
        for (Mapper p : pList) {
            Definition def = p.getDefinition();
            PropertyBinder b = p.getBinder(sess);

            if (def instanceof SearchableDefinition) {
                SearchableDefinition sd = (SearchableDefinition) def;
                List<String> objs = (List<String>) b.get(o);

                if (sd.getPreProcessor() != null) {
                    List<String> procd = new ArrayList<String>(objs.size());
                    for (String obj : objs)
                        procd.add(sd.getPreProcessor().process(o, sd, obj));
                    objs = procd;
                }

                addStmts(buf, id, sd.getUri(), objs, null, false, CollectionType.PREDICATE, "$s" + bnCtr++ + "i",
                        false);
                continue;
            }

            RdfDefinition rd = (RdfDefinition) def;
            if (!rd.isEntityOwned())
                continue;

            if (rd.isPredicateMap()) {
                Map<String, List<String>> pMap = (Map<String, List<String>>) b.getRawValue(o, true);
                for (Map.Entry<String, List<String>> stringListEntry : pMap.entrySet())
                    for (String v : stringListEntry.getValue())
                        addStmt(buf, id, stringListEntry.getKey(), v, null, false); // xxx: uri or literal?
            } else if (!rd.isAssociation())
                addStmts(buf, id, rd.getUri(), (List<String>) b.get(o), rd.getDataType(), rd.typeIsUri(),
                        rd.getColType(), "$s" + bnCtr++ + "i", rd.hasInverseUri());
            else
                addStmts(buf, id, rd.getUri(), sess.getIds(b.get(o)), null, true, rd.getColType(),
                        "$s" + bnCtr++ + "i", rd.hasInverseUri());
        }

        if (bnCtr > 1000000000)
            bnCtr = 0; // avoid negative numbers
    }

    private static void addStmts(StringBuilder buf, String subj, String pred, List<String> objs, String dt,
            boolean objIsUri, CollectionType mt, String prefix, boolean inverse) {
        int i = 0;
        switch (mt) {
        case PREDICATE:
            for (String obj : objs) {
                if (!inverse)
                    addStmt(buf, subj, pred, obj, dt, objIsUri);
                else
                    addStmt(buf, obj, pred, subj, dt, true);
            }
            break;

        case RDFLIST:
            if (objs.size() > 0)
                buf.append("<").append(subj).append("> <").append(pred).append("> ").append(prefix).append("0 ");
            for (String obj : objs) {
                addStmt(buf, prefix + i, "rdf:type", "rdf:List", null, true);
                addStmt(buf, prefix + i, "rdf:first", obj, dt, objIsUri);
                if (i > 0)
                    buf.append(prefix).append(i - 1).append(" <rdf:rest> ").append(prefix).append(i).append(" ");
                i++;
            }
            if (i > 0)
                addStmt(buf, prefix + (i - 1), "rdf:rest", "rdf:nil", null, true);
            break;

        case RDFBAG:
        case RDFSEQ:
        case RDFALT:
            String rdfType = (CollectionType.RDFBAG == mt) ? "<rdf:Bag> "
                    : ((CollectionType.RDFSEQ == mt) ? "<rdf:Seq> " : "<rdf:Alt> ");
            if (objs.size() > 0) {
                buf.append("<").append(subj).append("> <").append(pred).append("> ").append(prefix).append(" ");
                buf.append(prefix).append(" <rdf:type> ").append(rdfType);
            }
            for (String obj : objs)
                addStmt(buf, prefix, "rdf:_" + ++i, obj, dt, objIsUri);
            break;
        }
    }

    private static void addStmt(StringBuilder buf, String subj, String pred, String obj, String dt,
            boolean objIsUri) {
        if (!subj.startsWith("$"))
            buf.append("<").append(subj).append("> <").append(pred);
        else
            buf.append(subj).append(" <").append(pred);

        if (objIsUri) {
            buf.append("> <").append(obj).append("> ");
        } else {
            buf.append("> '").append(RdfUtil.escapeLiteral(obj));
            if (dt != null)
                buf.append("'^^<").append(dt).append("> ");
            else
                buf.append("' ");
        }
    }

    public <T> void delete(ClassMetadata cm, Collection<RdfMapper> fields, String id, T o, Connection con)
            throws OtmException {
        ItqlStoreConnection isc = (ItqlStoreConnection) con;

        final boolean partialDelete = (cm.getRdfMappers().size() != fields.size());
        Map<String, List<RdfMapper>> mappersByGraph = groupMappersByGraph(cm, fields);
        StringBuilder delete = new StringBuilder(500);

        // for every graph create a delete statement
        for (Map.Entry<String, List<RdfMapper>> stringListEntry : mappersByGraph.entrySet()) {
            int len = delete.length();
            delete.append("delete ");

            if (buildDeleteSelect(delete, getGraphUri(stringListEntry.getKey(), isc), stringListEntry.getValue(),
                    !partialDelete && stringListEntry.getKey().equals(cm.getGraph()) && cm.getAllTypes().size() > 0,
                    id))
                delete.append("from <").append(getGraphUri(stringListEntry.getKey(), isc)).append(">;");
            else
                delete.setLength(len);
        }

        doDelete(isc, delete.toString());
    }

    public <T> void remove(ClassMetadata cm, Collection<SearchableDefinition> fields, String id, T o,
            Connection con) throws OtmException {
        ItqlStoreConnection isc = (ItqlStoreConnection) con;

        Map<String, List<SearchableDefinition>> defsByGraph = groupMappersByGraph(cm, fields);
        StringBuilder delete = new StringBuilder(500);

        // for every index and graph create a delete statement
        for (Map.Entry<String, List<SearchableDefinition>> stringListEntry : defsByGraph.entrySet()) {
            Map<String, List<RdfMapper>> rdfMappersByGraph = groupMappersByGraph(cm,
                    findRdfMappersForSearch(cm, stringListEntry.getValue()));

            for (Map.Entry<String, List<RdfMapper>> stringListEntry1 : rdfMappersByGraph.entrySet()) {
                int len = delete.length();
                delete.append("delete ");

                if (buildDeleteSelect(delete, getGraphUri(stringListEntry1.getKey(), isc),
                        stringListEntry1.getValue(), false, id))
                    delete.append("from <").append(getGraphUri(stringListEntry.getKey(), isc)).append(">;");
                else
                    delete.setLength(len);
            }
        }

        doDelete(isc, delete.toString());
    }

    private static Collection<RdfMapper> findRdfMappersForSearch(ClassMetadata cm,
            List<SearchableDefinition> defs) {
        Collection<RdfMapper> mappers = new ArrayList<RdfMapper>();

        for (SearchableDefinition def : defs) {
            Mapper m = cm.getMapperByName(def.getLocalName());
            if (m instanceof RdfMapper)
                mappers.add((RdfMapper) m);
        }

        return mappers;
    }

    public <T> void remove(ClassMetadata cm, SearchableDefinition field, String id, T o, Connection con)
            throws OtmException {
        ItqlStoreConnection isc = (ItqlStoreConnection) con;

        String g = getGraphUri(field.getIndex(), isc);
        String c = "<" + id + "> <" + field.getUri() + "> $o";

        String tql = "delete select " + c + " from <" + g + "> where " + c + " from <" + g + ">;";
        doDelete(isc, tql);
    }

    private void doDelete(ItqlStoreConnection isc, String delete) throws OtmException {
        if (log.isDebugEnabled())
            log.debug("delete: " + delete);

        if (delete.length() == 0)
            return;

        try {
            isc.getItqlClient().doUpdate(delete);
        } catch (IOException ioe) {
            throw new OtmException("error performing update: " + delete, ioe);
        }
    }

    private boolean buildDeleteSelect(StringBuilder qry, String graph, List<RdfMapper> rdfMappers, boolean delTypes,
            String id) throws OtmException {
        qry.append("select $s $p $o from <").append(graph).append("> where ");
        int len = qry.length();

        // build forward statements
        boolean found = delTypes;
        boolean predicateMap = false;
        for (RdfMapper p : rdfMappers) {
            if (!p.hasInverseUri() && p.isEntityOwned()) {
                if (p.isPredicateMap()) {
                    predicateMap = true;
                    found = false;
                    break;
                }
                found = true;
            }
        }

        if (predicateMap)
            qry.append("($s $p $o and $s <mulgara:is> <").append(id).append(">) ");

        if (found) {
            qry.append("($s $p $o and $s <mulgara:is> <").append(id).append("> and (");
            if (delTypes)
                qry.append("$p <mulgara:is> <rdf:type> or ");
            for (RdfMapper p : rdfMappers) {
                if (!p.hasInverseUri() && p.isEntityOwned())
                    qry.append("$p <mulgara:is> <").append(p.getUri()).append("> or ");
            }
            qry.setLength(qry.length() - 4);
            qry.append(") ) ");
        }

        // build reverse statements
        boolean needDisj = found | predicateMap;
        found = false;
        for (RdfMapper p : rdfMappers) {
            if (p.hasInverseUri() && p.isEntityOwned()) {
                found = true;
                break;
            }
        }

        if (found) {
            if (needDisj)
                qry.append("or ");
            qry.append("($s $p $o and $o <mulgara:is> <").append(id).append("> and (");
            for (RdfMapper p : rdfMappers) {
                if (p.hasInverseUri() && p.isEntityOwned())
                    qry.append("$p <mulgara:is> <").append(p.getUri()).append("> or ");
            }
            qry.setLength(qry.length() - 4);
            qry.append(") ) ");
        }

        // build rdf:List, rdf:Bag, rdf:Seq or rdf:Alt statements
        Collection<RdfMapper> col = null;
        for (RdfMapper p : rdfMappers) {
            if (p.getColType() == CollectionType.RDFLIST) {
                if (col == null)
                    col = new ArrayList<RdfMapper>();
                col.add(p);
            }
        }

        if (col != null) {
            qry.append("or ($s $p $o and <").append(id).append("> $x $c ")
                    .append("and (trans($c <rdf:rest> $s) or $c <rdf:rest> $s or <").append(id).append("> $x $s)")
                    .append(" and $s <rdf:type> <rdf:List> and (");
            for (RdfMapper p : col)
                qry.append("$x <mulgara:is> <").append(p.getUri()).append("> or ");
            qry.setLength(qry.length() - 4);
            qry.append(") ) ");
        }

        col = null;
        for (RdfMapper p : rdfMappers) {
            if ((p.getColType() == CollectionType.RDFBAG) || (p.getColType() == CollectionType.RDFSEQ)
                    || (p.getColType() == CollectionType.RDFALT)) {
                if (col == null)
                    col = new ArrayList<RdfMapper>();
                col.add(p);
            }
        }

        if (col != null) {
            qry.append("or ($s $p $o and <").append(id).append("> $x $s ").append(" and ($s <rdf:type> <rdf:Bag> ")
                    .append("or $s <rdf:type> <rdf:Seq> or $s <rdf:type> <rdf:Alt>) and (");
            for (RdfMapper p : col)
                qry.append("$x <mulgara:is> <").append(p.getUri()).append("> or ");
            qry.setLength(qry.length() - 4);
            qry.append(") ) ");
        }

        return (qry.length() > len);
    }

    public TripleStore.Result get(ClassMetadata cm, final String id, Connection con, final List<Filter> filters,
            boolean filterObj) throws OtmException {
        final ItqlStoreConnection isc = (ItqlStoreConnection) con;
        // do query
        String get = buildGetSelect(cm, id, isc, filters, filterObj);
        if (log.isDebugEnabled())
            log.debug("get: " + get);

        List<Answer> ans;
        try {
            ans = isc.getItqlClient().doQuery(get);
        } catch (IOException ioe) {
            throw new OtmException("error performing query: " + get, ioe);
        } catch (AnswerException ae) {
            throw new OtmException("error performing query: " + get, ae);
        }

        // parse
        final Map<String, List<String>> fvalues = new HashMap<String, List<String>>();
        final Map<String, List<String>> rvalues = new HashMap<String, List<String>>();

        try {
            Map<String, List<String>> values = fvalues;
            for (Answer qa : ans) {
                if (qa.getVariables() == null)
                    throw new OtmException("query failed: " + qa.getMessage());

                if (values == null)
                    throw new OtmException("query failed: expecting 2 answers. got " + ans.size());

                // collect the results, grouping by predicate
                qa.beforeFirst();
                while (qa.next()) {
                    String p = qa.getString(0);
                    String o = qa.getString(1);

                    List<String> v = values.get(p);
                    if (v == null)
                        values.put(p, v = new ArrayList<String>());
                    v.add(o);
                }
                qa.close();
                // prepare to read inverse query results (if not already done)
                values = (values == rvalues) ? null : rvalues;
            }
        } catch (AnswerException ae) {
            throw new OtmException("Error parsing answer", ae);
        }

        return new TripleStore.Result() {
            public Map<String, List<String>> getFValues() {
                return fvalues;
            }

            public Map<String, List<String>> getRValues() {
                return rvalues;
            }
        };
    }

    private String buildGetSelect(ClassMetadata cm, String id, ItqlStoreConnection isc, List<Filter> filters,
            boolean filterObj) throws OtmException {
        String graphs = getGraphsExpr(Collections.singleton(cm), isc);
        StringBuilder qry = new StringBuilder(500);

        qry.append("select $p $o from ").append(graphs).append(" where ");
        qry.append("<").append(id).append("> $p $o");
        if (filterObj) {
            String subjConstr = " and $s <mulgara:is> <" + id + ">";
            if (applyObjectFilters(qry, cm, "$s", subjConstr, filters, isc.getSession()))
                qry.append(subjConstr);
        }

        qry.append("; select $p $s from ").append(graphs).append(" where ");
        qry.append("$s $p <").append(id).append(">");
        if (filterObj) {
            String subjConstr = " and $o <mulgara:is> <" + id + ">";
            if (applyObjectFilters(qry, cm, "$o", subjConstr, filters, isc.getSession()))
                qry.append(subjConstr);
        }
        qry.append(";");

        return qry.toString();
    }

    private boolean applyObjectFilters(StringBuilder qry, ClassMetadata cm, String var, String constr,
            List<Filter> filters, Session session) throws OtmException {
        // avoid work if possible
        if (filters == null || filters.size() == 0)
            return false;

        // find applicable filters
        SessionFactory sf = session.getSessionFactory();
        List<Pair<Filter, Boolean>> finfos = new ArrayList<Pair<Filter, Boolean>>();

        for (Filter f : filters) {
            ClassMetadata fcm = sf.getClassMetadata(f.getFilterDefinition().getFilteredClass());
            if (fcm.isAssignableFrom(cm, session.getEntityMode()))
                finfos.add(new Pair<Filter, Boolean>(f, true));
            else if (cm.isAssignableFrom(fcm, session.getEntityMode()))
                finfos.add(new Pair<Filter, Boolean>(f, false));
        }

        if (finfos.size() == 0)
            return false;

        // apply filters
        qry.append(" and (");

        int idx = 0;
        for (Pair<Filter, Boolean> i : finfos) {
            qry.append("(");
            ItqlCriteria.buildFilter((AbstractFilterImpl) i.first(), i.second(), qry, var, constr, "$gof" + idx++,
                    session);
            qry.append(") or ");
        }

        qry.setLength(qry.length() - 4);
        qry.append(")");

        return true;
    }

    private static String getGraphsExpr(Set<? extends ClassMetadata> cmList, ItqlStoreConnection isc)
            throws OtmException {
        Set<String> gList = new HashSet<String>();
        for (ClassMetadata cm : cmList) {
            gList.add(getGraphUri(cm.getGraph(), isc));
            for (RdfMapper p : cm.getRdfMappers()) {
                if (p.getGraph() != null)
                    gList.add(getGraphUri(p.getGraph(), isc));
            }
        }

        StringBuilder mexpr = new StringBuilder(100);
        for (String g : gList)
            mexpr.append("<").append(g).append("> or ");
        mexpr.setLength(mexpr.length() - 4);

        return mexpr.toString();
    }

    public List<String> getRdfList(String sub, String pred, String graphUri, Connection con) throws OtmException {
        ItqlStoreConnection isc = (ItqlStoreConnection) con;
        StringBuilder qry = new StringBuilder(500);
        qry.append("select $o $s $n from <").append(graphUri).append("> where ")
                .append("(trans($c <rdf:rest> $s) or $c <rdf:rest> $s or <").append(sub).append("> <").append(pred)
                .append("> $s) and <").append(sub).append("> <").append(pred).append("> $c and $s <rdf:first> $o")
                .append(" and $s <rdf:rest> $n;");

        return execCollectionsQry(qry.toString(), isc);
    }

    public List<String> getRdfBag(String sub, String pred, String graphUri, Connection con) throws OtmException {
        ItqlStoreConnection isc = (ItqlStoreConnection) con;

        /* Optimization note: using
         *   ($s $p $o and <foo> <bar> $s) minus ($s <rdf:type> $o and <foo> <bar> $s)
         * is (more than 30 times) faster than the more succinct
         *   ($s $p $o minus $s <rdf:type> $o) and <foo> <bar> $s
         */
        StringBuilder qry = new StringBuilder(500);
        qry.append("select $o $p from <").append(graphUri).append("> where ").append("(($s $p $o and <").append(sub)
                .append("> <").append(pred).append("> $s) minus ").append("($s <rdf:type> $o and <").append(sub)
                .append("> <").append(pred).append("> $s));");

        return execCollectionsQry(qry.toString(), isc);
    }

    private List<String> execCollectionsQry(String qry, ItqlStoreConnection isc) throws OtmException {
        if (log.isDebugEnabled())
            log.debug("rdf:List/rdf:Bag query : " + qry);

        List<Answer> ans;
        try {
            ans = isc.getItqlClient().doQuery(qry);
        } catch (IOException ioe) {
            throw new OtmException("error performing rdf:List/rdf:Bag query", ioe);
        } catch (AnswerException ae) {
            throw new OtmException("error performing rdf:List/rdf:Bag query", ae);
        }

        List<String> res = new ArrayList<String>();

        try {
            // check if we got something useful
            Answer qa = ans.get(0);
            if (qa.getVariables() == null)
                throw new OtmException("query failed: " + qa.getMessage());

            // collect the results,
            qa.beforeFirst();
            boolean isRdfList = qa.indexOf("n") != -1;
            if (isRdfList) {
                Map<String, String> fwd = new HashMap<String, String>();
                Map<String, String> rev = new HashMap<String, String>();
                Map<String, String> objs = new HashMap<String, String>();
                while (qa.next()) {
                    String assoc = qa.getString("o");
                    String node = qa.getString(1);
                    String next = qa.getString(2);
                    objs.put(node, assoc);
                    fwd.put(node, next);
                    rev.put(next, node);
                }

                if (objs.size() == 0)
                    return res;

                String first = rev.keySet().iterator().next();
                while (rev.containsKey(first))
                    first = rev.get(first);
                for (String n = first; fwd.get(n) != null; n = fwd.get(n))
                    res.add(objs.get(n));
            } else {
                while (qa.next()) {
                    String p = qa.getString("p");
                    if (!p.startsWith(Rdf.rdf + "_"))
                        continue; // an un-recognized predicate
                    int i = Integer.decode(p.substring(Rdf.rdf.length() + 1)) - 1;
                    while (i >= res.size())
                        res.add(null);
                    String assoc = qa.getString("o");
                    res.set(i, assoc);
                }
            }
            qa.close();
        } catch (AnswerException ae) {
            throw new OtmException("Error parsing answer", ae);
        }
        return res;
    }

    public List list(Criteria criteria, Connection con) throws OtmException {
        ItqlCriteria ic = new ItqlCriteria(criteria);
        String qry = ic.buildUserQuery();
        log.debug("list: " + qry);
        List<Answer> ans;
        try {
            ItqlStoreConnection isc = (ItqlStoreConnection) con;
            ans = isc.getItqlClient().doQuery(qry);
        } catch (IOException ioe) {
            throw new OtmException("error performing query: " + qry, ioe);
        } catch (AnswerException ae) {
            throw new OtmException("error performing query: " + qry, ae);
        }
        return ic.createResults(ans);
    }

    public Results doQuery(GenericQuery query, Collection<Filter> filters, Connection con) throws OtmException {
        ItqlStoreConnection isc = (ItqlStoreConnection) con;

        ItqlQuery iq = new ItqlQuery(query, filters, isc.getSession());
        QueryInfo qi = iq.parseItqlQuery();

        List<Answer> ans;
        try {
            ans = isc.getItqlClient().doQuery(qi.getQuery());
        } catch (IOException ioe) {
            throw new QueryException("error performing query '" + query + "'", ioe);
        } catch (AnswerException ae) {
            throw new QueryException("error performing query '" + query + "'", ae);
        }

        return new ItqlOQLResults(ans.get(0), qi, iq.getWarnings().toArray(new String[iq.getWarnings().size()]),
                isc.getSession());
    }

    public Results doNativeQuery(String query, Connection con) throws OtmException {
        ItqlStoreConnection isc = (ItqlStoreConnection) con;
        List<Answer> ans;
        try {
            ans = isc.getItqlClient().doQuery(query);
        } catch (IOException ioe) {
            throw new QueryException("error performing query '" + query + "'", ioe);
        } catch (AnswerException ae) {
            throw new QueryException("error performing query '" + query + "'", ae);
        }

        if (ans.get(0).getMessage() != null)
            throw new QueryException("error performing query '" + query + "' - message was: "
                    + ans.get(0).getMessage() + "\nDid you attempt to use doNativeQuery for a non-query command?"
                    + " If so, please use doNativeUpdate instead");

        return new ItqlNativeResults(ans.get(0), isc.getSession());
    }

    public void doNativeUpdate(String command, Connection con) throws OtmException {
        ItqlStoreConnection isc = (ItqlStoreConnection) con;
        try {
            isc.getItqlClient().doUpdate(command);
        } catch (IOException ioe) {
            throw new QueryException("error performing command '" + command + "'", ioe);
        }
    }

    private static String getGraphUri(String graphId, ItqlStoreConnection isc) throws OtmException {
        GraphConfig mc = isc.getSession().getSessionFactory().getGraph(graphId);
        if (mc == null) // Happens if using a Class but the graph was not added
            throw new OtmException("Unable to find graph '" + graphId + "'");
        return mc.getUri().toString();
    }

    private ItqlClient getItqlClient(Map<String, String> aliases, boolean useCache) throws OtmException {
        ItqlClient res = null;

        while (useCache && (res = removeTopEntry()) != null) {
            try {
                res.ping();

                if (log.isDebugEnabled())
                    log.debug("Got itql-client from connection-cache: " + res);
                break;
            } catch (IOException ioe) {
                res.close();
                if (log.isDebugEnabled())
                    log.debug("Discarding failed itql-client: " + res, ioe);
            }
        }

        if (res == null) {
            try {
                res = itqlFactory.createClient(serverUri);
                if (log.isDebugEnabled())
                    log.debug("Created new itql-client: " + res);
            } catch (Exception e) {
                throw new OtmException("Error talking to '" + serverUri + "'", e);
            }
        }

        if (aliases != null)
            res.setAliases(aliases);

        return res;
    }

    private ItqlClient removeTopEntry() {
        synchronized (conCache) {
            return conCache.isEmpty() ? null : conCache.remove(conCache.size() - 1);
        }
    }

    private void returnItqlClient(ItqlClient itql) {
        if (log.isDebugEnabled())
            log.debug("Returning itql-client to connection-cache: " + itql);

        synchronized (conCache) {
            conCache.add(itql);
        }
    }

    /**
     * Creates a new graph in the TQL store.
     *
     * @param conf the configuration
     * @param con a TQL connection
     * @throws OtmException on an error
     */
    public void createGraph(GraphConfig conf, Connection con) throws OtmException {
        ItqlStoreConnection isc = (ItqlStoreConnection) con;
        String type = (conf.getType() == null) ? "mulgara:Model" : conf.getType().toString();
        try {
            isc.getItqlClient().doUpdate("create <" + conf.getUri() + "> <" + type + ">;");
        } catch (Exception e) {
            throw new OtmException("Failed to create graph <" + conf.getUri() + ">", e);
        }
    }

    /**
     * Drops a graph in the persistence store, deleting all triples.
     *
     * @param conf the configuration
     * @param con a TQL connection
     *
     * @throws OtmException on an error
     */
    public void dropGraph(GraphConfig conf, Connection con) throws OtmException {
        ItqlStoreConnection isc = (ItqlStoreConnection) con;
        try {
            isc.getItqlClient().doUpdate("drop <" + conf.getUri() + ">;");
        } catch (Exception e) {
            throw new OtmException("Failed to remove graph <" + conf.getUri() + ">", e);
        }
    }

    private class ItqlStoreConnection extends AbstractConnection {
        private ItqlClient itql;
        private Map<String, StringBuilder> inserts = new HashMap<String, StringBuilder>();

        public ItqlStoreConnection(Session sess, boolean readOnly) throws OtmException {
            super(sess);
            itql = ItqlStore.this.getItqlClient(sess.getSessionFactory().listAliases(), true);
            itql.clearLastError();

            try {
                enlistResource(readOnly ? itql.getReadOnlyXAResource() : itql.getXAResource());
            } catch (IOException ioe) {
                throw new OtmException("Error getting xa-resource", ioe);
            }
        }

        public ItqlClient getItqlClient() throws OtmException {
            return itql;
        }

        public Map<String, StringBuilder> getInserts() {
            return inserts;
        }

        public void close() {
            Exception e = itql.getLastError();
            if (!(e instanceof IOException || e != null && e.getCause() instanceof IOException))
                returnItqlClient(itql);
            else
                itql.close();
            itql = null;
        }
    }
}