Package org.openbel.framework.api.internal

Source Code of org.openbel.framework.api.internal.KAMStoreDaoImpl$BelTerm

/**
* Copyright (C) 2012-2013 Selventa, Inc.
*
* This file is part of the OpenBEL Framework.
*
* This program is free software; you can redistribute it and/or modify it
* 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.
*
* The OpenBEL Framework is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the OpenBEL Framework. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms under LGPL v3:
*
* This license does not authorize you and you are prohibited from using the
* name, trademarks, service marks, logos or similar indicia of Selventa, Inc.,
* or, in the discretion of other licensors or authors of the program, the
* name, trademarks, service marks, logos or similar indicia of such authors or
* licensors, in any marketing or advertising materials relating to your
* distribution of the program or any covered product. This restriction does
* not waive or limit your obligation to keep intact all copyright notices set
* forth in the program as delivered to you.
*
* If you distribute the program in whole or in part, or any modified version
* of the program, and you assume contractual liability to the recipient with
* respect to the program or modified version, then you will indemnify the
* authors and licensors of the program for any liabilities that these
* contractual assumptions directly impose on those licensors and authors.
*/
package org.openbel.framework.api.internal;

import static java.lang.String.format;
import static java.sql.ResultSet.CONCUR_READ_ONLY;
import static java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE;
import static java.util.Collections.emptyList;
import static org.openbel.framework.common.BELUtilities.noLength;
import static org.openbel.framework.common.BELUtilities.quoteParameter;
import static org.openbel.framework.common.BELUtilities.sizedHashMap;
import static org.openbel.framework.common.BELUtilities.sizedHashSet;
import static org.openbel.framework.common.BELUtilities.substringEquals;
import static org.openbel.framework.common.enums.RelationshipType.fromValue;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.openbel.framework.api.AllocatingIterator;
import org.openbel.framework.api.AnnotationFilterCriteria;
import org.openbel.framework.api.BelDocumentFilterCriteria;
import org.openbel.framework.api.CitationFilterCriteria;
import org.openbel.framework.api.FilterCriteria;
import org.openbel.framework.api.Kam;
import org.openbel.framework.api.Kam.KamEdge;
import org.openbel.framework.api.Kam.KamNode;
import org.openbel.framework.api.KamElementImpl;
import org.openbel.framework.api.KamStoreObjectImpl;
import org.openbel.framework.api.NamespaceFilterCriteria;
import org.openbel.framework.api.RelationshipTypeFilterCriteria;
import org.openbel.framework.api.SimpleKAMEdge;
import org.openbel.framework.api.SimpleKAMNode;
import org.openbel.framework.api.internal.KAMCatalogDao.AnnotationFilter;
import org.openbel.framework.api.internal.KAMCatalogDao.KamFilter;
import org.openbel.framework.api.internal.KAMCatalogDao.KamInfo;
import org.openbel.framework.api.internal.KAMCatalogDao.NamespaceFilter;
import org.openbel.framework.common.AnnotationDefinitionResolutionException;
import org.openbel.framework.common.BELUtilities;
import org.openbel.framework.common.InvalidArgument;
import org.openbel.framework.common.bel.parser.BELParser;
import org.openbel.framework.common.enums.CitationType;
import org.openbel.framework.common.enums.FunctionEnum;
import org.openbel.framework.common.enums.RelationshipType;
import org.openbel.framework.common.model.AnnotationDefinition;
import org.openbel.framework.common.model.CitationAuthorsAnnotationDefinition;
import org.openbel.framework.common.model.CitationCommentAnnotationDefinition;
import org.openbel.framework.common.model.CitationDateAnnotationDefinition;
import org.openbel.framework.common.model.CitationNameAnnotationDefinition;
import org.openbel.framework.common.model.CitationReferenceAnnotationDefinition;
import org.openbel.framework.common.model.CitationTypeAnnotationDefinition;
import org.openbel.framework.common.model.Parameter;
import org.openbel.framework.common.model.Term;
import org.openbel.framework.common.protonetwork.model.SkinnyUUID;
import org.openbel.framework.common.util.PackUtils;
import org.openbel.framework.common.util.Pair;
import org.openbel.framework.core.df.AbstractJdbcDAO;
import org.openbel.framework.core.df.DBConnection;
import org.openbel.framework.core.df.external.CacheableAnnotationDefinitionService;
import org.openbel.framework.core.df.external.CacheableAnnotationDefinitionServiceImpl;
import org.openbel.framework.core.df.external.ExternalResourceException;

/**
*
* @author Julian Ray {@code jray@selventa.com}
*
*/
public final class KAMStoreDaoImpl extends AbstractJdbcDAO implements
        KAMStoreDao {

    private static final String SELECT_COUNT_NODES =
            "SELECT COUNT(*) FROM @.kam_node kn";
    private static final String SELECT_COUNT_EDGES =
            "SELECT COUNT(*) FROM @.kam_edge ke";
    private static final String SELECT_PROTO_NODES_SQL =
            "SELECT kn.kam_node_id, kn.function_type_id, kn.node_label_oid FROM @.kam_node kn";
    private static final String SELECT_PROTO_EDGES_SQL =
            "SELECT ke.kam_edge_id, ke.kam_source_node_id, ke.relationship_type_id, ke.kam_target_node_id FROM @.kam_edge ke";
    private static final String SELECT_OBJECTS_VALUE_SQL =
            "SELECT type_id, varchar_value, objects_text_id FROM @.objects WHERE objects_id = ?";
    private static final String SELECT_OBJECTS_TEXT_SQL =
            "SELECT text_value FROM @.objects_text WHERE objects_text_id = ?";
    private static final String SELECT_OBJECTS_ID_SQL =
            "SELECT objects_id from @.objects WHERE varchar_value = ?";
    private static final String SELECT_STATEMENTS_BY_EDGE_SQL =
            "select "
                    +
                    "s.statement_id, document_id, subject_term_id, relationship_type_id, object_term_id, nested_subject_id, nested_relationship_type_id, nested_object_id "
                    +
                    "FROM @.statement s, @.kam_edge_statement_map kesm WHERE s.statement_id = kesm.statement_id and kesm.kam_edge_id = ?";
    private static final String SELECT_TERM_BY_ID_SQL =
            "SELECT term_label_oid FROM @.term WHERE term_id = ?";
    private static final String SELECT_KAM_NODE_ID_BY_TERM_ID_SQL =
            "SELECT kam_node_id FROM @.term WHERE term_id = ?";
    private static final String SELECT_DOCUMENT_BY_ID_SQL =
            "SELECT document_id, name, description, version, copyright, disclaimer, contact_information, license_information, authors FROM @.document_header_information WHERE document_id = ?";
    private static final String SELECT_ANNOTATION_BY_ID_SQL =
            "SELECT value_oid, annotation_definition_id FROM @.annotation WHERE annotation_id = ?";
    private static final String SELECT_ANNOTATIONS_BY_STATEMENT_ID_SQL =
            "SELECT b.annotation_id FROM @.statement_annotation_map a LEFT JOIN @.annotation b ON a.annotation_id = b.annotation_id WHERE a.statement_id = ?";
    private static final String SELECT_ANNOTATION_TYPE_BY_ID_SQL =
            "SELECT annotation_definition_id, name, description, annotation_usage, annotation_definition_type_id FROM @.annotation_definition WHERE annotation_definition_id = ?";
    private static final String SELECT_ANNOTATION_TYPES_SQL =
            "SELECT annotation_definition_id, name, description, annotation_usage, annotation_definition_type_id FROM @.annotation_definition";
    private static final String SELECT_ANNOTATION_TYPES_BY_DOCUMENT_ID_SQL =
            "SELECT annotation_definition_id FROM @.document_annotation_def_map WHERE document_id = ?";
    private static final String SELECT_ANNOTATION_TYPE_DOMAIN_VALUE_SQL =
            "SELECT domain_value_oid, annotation_definition_type_id FROM @.annotation_definition WHERE annotation_definition_id = ?";
    private static final String SELECT_KAM_NODE_PARAMETERS_PREFIX_SQL =
            "SELECT knp.kam_node_id, knp.kam_global_parameter_id FROM @.kam_node_parameter knp";
    private static final String SELECT_KAM_NODE_PARAMETERS_ORDER_SQL =
            " ORDER BY knp.kam_node_id, knp.ordinal";
    private static final String SELECT_NAMESPACES_BY_DOCUMENT_ID_SQL =
            "SELECT namespace_id FROM @.document_namespace_map WHERE document_id = ?";
    private static final String SELECT_NAMESPACE_BY_PREFIX_SQL =
            "SELECT namespace_id, prefix, resource_location_oid FROM @.namespace WHERE prefix = ?";
    private static final String SELECT_DOCUMENTS_SQL =
            "SELECT document_id, name, description, version, copyright, disclaimer, contact_information, license_information, authors FROM @.document_header_information";
    private static final String SELECT_NAMESPACES_SQL =
            "SELECT namespace_id, prefix, resource_location_oid FROM @.namespace";
    private static final String SELECT_NAMESPACE_BY_ID_SQL =
            "SELECT namespace_id, prefix, resource_location_oid FROM @.namespace WHERE namespace_id = ?";
    private static final String SELECT_TERM_PARAMETERS_BY_TERM_ID_SQL =
            "SELECT term_parameter_id, namespace_id, parameter_value_oid FROM @.term_parameter WHERE term_id = ? ORDER BY ordinal";
    private static final String SELECT_KAM_NODE_IDS_FOR_PARAMETER_FUNCTION_SQL =
            "SELECT DISTINCT(k.kam_node_id) FROM @.kam_node k LEFT JOIN @.term t ON k.kam_node_id = t.kam_node_id LEFT JOIN @.term_parameter tp ON t.term_id = tp.term_id WHERE k.function_type_id = ? AND (tp.namespace_id = ? OR (tp.namespace_id IS NULL AND ? IS NULL)) AND tp.parameter_value_oid = ?";
    private static final String SELECT_KAM_NODE_IDS_FOR_PARAMETER_SQL =
            "SELECT DISTINCT(k.kam_node_id) FROM @.kam_node k LEFT JOIN @.term t ON k.kam_node_id = t.kam_node_id LEFT JOIN @.term_parameter tp ON t.term_id = tp.term_id WHERE (tp.namespace_id = ? OR (tp.namespace_id IS NULL AND ? IS NULL)) AND tp.parameter_value_oid = ?";
    private static final String SELECT_KAM_NODE_IDS_FOR_UUID_FUNCTION_SQL =
            "SELECT k.kam_node_id FROM @.kam_node k INNER JOIN @.kam_node_parameter p ON k.kam_node_id = p.kam_node_id INNER JOIN @.kam_parameter_uuid u ON p.kam_global_parameter_id = u.kam_global_parameter_id WHERE k.function_type_id = ? AND u.most_significant_bits = ? AND u.least_significant_bits = ?";
    private static final String SELECT_KAM_NODE_IDS_FOR_UUID_SQL =
            "SELECT DISTINCT(p.kam_node_id) FROM @.kam_node_parameter p LEFT JOIN @.kam_parameter_uuid u ON p.kam_global_parameter_id = u.kam_global_parameter_id WHERE u.most_significant_bits = ? AND u.least_significant_bits = ?";
    private static final String SELECT_STATEMENT_BY_ID_SQL =
            "SELECT statement_id, document_id, subject_term_id, relationship_type_id, object_term_id, nested_subject_id, nested_relationship_type_id, nested_object_id FROM @.statement WHERE statement_id = ?";
    private static final String SELECT_TERMS_IDS_BY_NODE_ID_SQL =
            "SELECT term_id FROM @.term WHERE kam_node_id = ?";
    private static final String SELECT_KAM_NODES_CONTAINING_KAM_NODE_PARAMETER_SQL =
            "SELECT DISTINCT(knp2.kam_node_id) FROM @.kam_node_parameter knp1, @.kam_node_parameter knp2 where knp1.kam_node_id = ? AND knp1.kam_global_parameter_id = knp2.kam_global_parameter_id AND knp2.kam_node_id != ?";
    private static final String SELECT_CITATION_ANNOTATIONS_SQL =
            "SELECT sam.annotation_id, sam.statement_id, s.document_id FROM @.statement s LEFT JOIN @.statement_annotation_map sam ON s.statement_id = sam.statement_id LEFT JOIN @.annotation a ON a.annotation_id = sam.annotation_id LEFT JOIN @.annotation_definition ad ON ad.annotation_definition_id = a.annotation_definition_id WHERE ad.name IN ('"
                    + StringUtils
                            .join(KAMStoreConstants.CITATION_ANNOTATION_DEFINITION_IDS,
                                    "', '") + "') order by sam.statement_id";
    private static final String SELECT_TERM_ID_BY_PARAMETERS_SQL =
            "SELECT term_id FROM @.term_parameter WHERE (namespace_id = ? OR (namespace_id IS NULL AND ? IS NULL)) AND parameter_value_oid = ? AND ordinal = ?";
    private static final String SELECT_KAM_NODE_UUIDS_SQL =
            "SELECT kn.kam_node_id, most_significant_bits, least_significant_bits " +
            "FROM @.kam_node kn, @.kam_node_parameter knp, @.kam_parameter_uuid kpu " +
            "WHERE kn.kam_node_id = knp.kam_node_id AND " +
            "knp.kam_global_parameter_id = kpu.kam_global_parameter_id " +
            "ORDER BY kn.kam_node_id";
    private static final String SELECT_KAM_NODE_BY_LABEL_SQL =
            "SELECT kn.kam_node_id " +
            "FROM @.kam_node kn, @.objects o " +
            "WHERE kn.node_label_oid = o.objects_id and o.varchar_value = ?";
    private static final String $TERM_PARAMETER_TABLE = "@.term_parameter tp%d";
    private static final String $KAM_PARAMETER_UUID_TABLE = "@.kam_parameter_uuid kpu%d";
    private static final String $TERM_ID_JOIN = "tp%d.term_id = t.term_id ";
    private static final String $ORDINAL_JOIN = "tp%d.ordinal = %d ";
    private static final String $KAM_UUID_JOIN = "tp%d.kam_global_parameter_id = kpu%d.kam_global_parameter_id ";
    private static final String $UUID_JOIN = "(kpu%d.most_significant_bits = ? AND kpu%d.least_significant_bits = ?) ";
    private static final String SELECT_KAM_NODE_BY_TERM_UUIDS =
            "SELECT kn.kam_node_id " +
            "FROM @.kam_node kn, @.term t, @.objects ot, %s, %s " +
            "WHERE kn.function_type_id = ? AND t.kam_node_id = kn.kam_node_id AND t.term_label_oid = ot.objects_id AND ot.varchar_value = ? AND " +
            "%s AND %s AND %s AND ( %s )";

    private static final String ANY_NUMBER_PLACEHOLDER = "#";
    private static final int ANY_NUMBER_PLACEHOLDER_LENGTH =
            ANY_NUMBER_PLACEHOLDER.length();
    private static final Pattern NUMBER_REGEX_PATTERN = Pattern.compile("\\d+");
    private static final Pattern ANY_NUMBER_REGEX_PATTERN = Pattern
            .compile("\\d+|" + Pattern.quote(ANY_NUMBER_PLACEHOLDER));
    private static final String CITATION = "Citation";
    private static final Set<String> citationTypes = getCitationDefinitions();

    // Caches
    private Map<Integer, BelTerm> termCache =
            new ConcurrentHashMap<Integer, BelTerm>();
    private Map<Integer, String> objectValueCache =
            new ConcurrentHashMap<Integer, String>();
    private Map<String, Integer> objectValueReverseCache =
            new ConcurrentHashMap<String, Integer>();
    private Map<Integer, Namespace> namespaceCache =
            new ConcurrentHashMap<Integer, Namespace>();
    private Map<String, Namespace> namespacePrefixCache =
            new ConcurrentHashMap<String, Namespace>();
    private Map<Integer, AnnotationType> annotationTypeCache =
            new ConcurrentHashMap<Integer, AnnotationType>();
    private Map<Integer, List<BelStatement>> supportingEvidenceCache =
            new ConcurrentHashMap<Integer, List<BelStatement>>();
    private Map<Integer, List<BelTerm>> supportingTermCache =
            new ConcurrentHashMap<Integer, List<BelTerm>>();
    private Map<String, Integer> supportingTermLabelReverseCache =
            new ConcurrentHashMap<String, Integer>();
    private Map<Integer, Integer> kamNodeTermCache =
            new ConcurrentHashMap<Integer, Integer>();

    private Map<Integer, BelStatement> statementCache =
            new ConcurrentHashMap<Integer, BelStatement>();
    private Map<Integer, BelDocumentInfo> documentCache =
            new ConcurrentHashMap<Integer, BelDocumentInfo>();
    private Map<Integer, List<TermParameter>> termParameterCache =
            new ConcurrentHashMap<Integer, List<TermParameter>>();
    private Map<Integer, Annotation> annotationCache =
            new ConcurrentHashMap<Integer, KAMStoreDaoImpl.Annotation>();
    private Map<Integer, List<Annotation>> statementAnnotationCache =
            new ConcurrentHashMap<Integer, List<Annotation>>();
    private Map<Integer, List<String>> annotationTypeValueCache =
            new ConcurrentHashMap<Integer, List<String>>();
    private Map<Integer, List<Integer>> nodeExampleMatchCache =
            new ConcurrentHashMap<Integer, List<Integer>>();

    //citation caches
    private Map<String, Citation> citationMap = null;
    private Map<Integer, List<Citation>> belDocumentCitationsMap = null;

    private CacheableAnnotationDefinitionService cacheableAnnotationDefinitionService;

    private static SimpleDateFormat dateFormat = new SimpleDateFormat(
            KAMStoreConstants.DATE_FORMAT);

    /**
     * Creates a KAMStoreDaoImpl from the Jdbc {@link Connection} that will
     * be used to load the KAM.
     *
     * @param dbc {@link Connection}, the database connection which should be
     * non-null and already open for sql execution.
     * @throws InvalidArgument - Thrown if {@code dbc} is null or the sql
     * connection is already closed.
     * @throws SQLException - Thrown if a sql error occurred while loading
     * the KAM.
     */
    public KAMStoreDaoImpl(String schemaName, DBConnection dbc)
            throws SQLException {
        super(dbc, schemaName);

        if (dbc == null) {
            throw new InvalidArgument("dbc is null");
        }

        if (dbc.getConnection().isClosed()) {
            throw new InvalidArgument("dbc is closed and cannot be used");
        }
        this.cacheableAnnotationDefinitionService =
                new CacheableAnnotationDefinitionServiceImpl();
    }

    @Override
    public List<BelDocumentInfo> getBelDocumentInfos() throws SQLException {
        List<BelDocumentInfo> list = new ArrayList<BelDocumentInfo>();

        ResultSet rset = null;

        try {
            PreparedStatement ps = getPreparedStatement(SELECT_DOCUMENTS_SQL);

            rset = ps.executeQuery();

            while (rset.next()) {
                BelDocumentInfo belDocumentInfo = getBelDocumentInfo(rset);
                list.add(belDocumentInfo);

                // This might save us time later on
                if (!documentCache.containsKey(belDocumentInfo.getId())) {
                    documentCache.put(belDocumentInfo.getId(), belDocumentInfo);
                }
            }
        } finally {
            close(rset);
        }

        return list;
    }

    @Override
    public List<Namespace> getNamespaces() throws SQLException {
        List<Namespace> list = new ArrayList<Namespace>();

        ResultSet rset = null;

        try {
            PreparedStatement ps = getPreparedStatement(SELECT_NAMESPACES_SQL);

            rset = ps.executeQuery();

            while (rset.next()) {
                Namespace namespace = getNamespace(rset);
                list.add(namespace);
                // This might save us time later on
                if (!namespaceCache.containsKey(namespace.getId())) {
                    namespaceCache.put(namespace.getId(), namespace);
                }
                if (!namespacePrefixCache.containsKey(namespace.getPrefix())) {
                    namespacePrefixCache.put(namespace.getPrefix(), namespace);
                }
            }
        } finally {
            close(rset);
        }

        return list;
    }

    @Override
    public BelStatement getBelStatement(Integer belStatementId)
            throws SQLException {

        // See if this statement is already cached
        if (statementCache.containsKey(belStatementId)) {
            return statementCache.get(belStatementId);
        }

        BelStatement belStatement = null;
        ResultSet rset = null;
        PreparedStatement ps = null;
        try {
            ps = getPreparedStatement(SELECT_STATEMENT_BY_ID_SQL);
            ps.setInt(1, belStatementId);

            rset = ps.executeQuery();

            if (rset.next()) {
                belStatement = getStatement(rset);
            }
        } finally {
            close(rset);
            close(ps);
        }

        // put this list into the evidence cache
        statementCache.put(belStatementId, belStatement);
        return belStatement;
    }

    @Override
    public List<BelStatement> getSupportingEvidence(KamEdge kamEdge)
            throws SQLException {
        return getSupportingEvidence(kamEdge.getId());
    }

    @Override
    public List<BelStatement> getSupportingEvidence(KamEdge kamEdge,
            AnnotationFilter filter) throws SQLException {
        final List<BelStatement> stmts = getSupportingEvidence(kamEdge);

        if (filter == null) {
            return stmts;
        }

        final List<FilterCriteria> criteria = filter.getFilterCriteria();
        final Map<AnnotationType, AnnotationFilterCriteria> amap =
                sizedHashMap(criteria.size());
        for (final FilterCriteria c : criteria) {
            final AnnotationFilterCriteria afc = (AnnotationFilterCriteria) c;
            amap.put(afc.getAnnotationType(), afc);
        }

        final Iterator<BelStatement> stmtIt = stmts.iterator();
        while (stmtIt.hasNext()) {
            final BelStatement stmt = stmtIt.next();

            final List<Annotation> annotations = stmt.getAnnotationList();

            for (final FilterCriteria c : criteria) {
                // criteria is invalid, continue
                if (c == null) {
                    continue;
                }

                final AnnotationFilterCriteria afc =
                        (AnnotationFilterCriteria) c;

                // criteria's annotation type is invalid, continue
                if (afc.getAnnotationType() == null) {
                    continue;
                }

                Annotation matchedAnnotation = null;
                for (final Annotation annotation : annotations) {
                    if (annotation.getAnnotationType() == afc
                            .getAnnotationType()) {
                        matchedAnnotation = annotation;
                    }
                }

                if (matchedAnnotation == null) {
                    if (c.isInclude()) {
                        stmtIt.remove();
                    }
                } else {
                    boolean valueMatch = afc.getValues().contains(
                            matchedAnnotation.getValue());
                    if (valueMatch && !c.isInclude()) {
                        stmtIt.remove();
                    }
                }
            }
        }

        return stmts;
    }

    /**
     *
     * @param kamEdgeId
     * @return
     * @throws SQLException
     */
    @Override
    public List<BelStatement> getSupportingEvidence(Integer kamEdgeId)
            throws SQLException {

        if (kamEdgeId == null) {
            throw new IllegalArgumentException("KAM edge ID cannot be null.");
        }
        // See if this evidence is already cached
        if (supportingEvidenceCache.containsKey(kamEdgeId)) {
            return supportingEvidenceCache.get(kamEdgeId);
        }

        List<BelStatement> list = new ArrayList<BelStatement>();

        ResultSet rset = null;

        try {
            PreparedStatement ps =
                    getPreparedStatement(SELECT_STATEMENTS_BY_EDGE_SQL);
            ps.setInt(1, kamEdgeId);

            rset = ps.executeQuery();

            while (rset.next()) {
                list.add(getStatement(rset));
            }
        } finally {
            close(rset);
        }

        // put this list into the evidence cache
        supportingEvidenceCache.put(kamEdgeId, list);
        return list;
    }

    @Override
    public List<BelTerm> getSupportingTerms(KamNode kamNode,
            NamespaceFilter namespaceFilter) throws SQLException {
        List<BelTerm> termList = getSupportingTerms(kamNode);

        if (namespaceFilter != null) {
            for (FilterCriteria criterion : namespaceFilter.getFilterCriteria()) {
                NamespaceFilterCriteria nfc =
                        (NamespaceFilterCriteria) criterion;
                Set<Integer> targetNamespaceIds = new HashSet<Integer>();
                for (Namespace ns : nfc.getValues()) {
                    targetNamespaceIds.add(ns.getId());
                }
                List<BelTerm> matchedTerms = new ArrayList<BelTerm>();
                for (BelTerm term : termList) {
                    List<TermParameter> params = getTermParameters(term);
                    for (TermParameter param : params) {
                        if (param.namespace != null
                                && targetNamespaceIds.contains(param.namespace
                                        .getId())) {
                            matchedTerms.add(term);
                            break;
                        }
                    }
                }
                if (criterion.isInclude()) {
                    termList.retainAll(matchedTerms);
                } else {
                    // must be exclude
                    termList.removeAll(matchedTerms);
                }

            }
        }
        return termList;
    }

    @Override
    public List<BelTerm> getSupportingTerms(KamNode kamNode)
            throws SQLException {
        return getSupportingTerms(kamNode.getId());
    }

    private List<BelTerm> getSupportingTerms(Integer kamNodeId)
            throws SQLException {

        // See if this evidence is already cached
        if (supportingTermCache.containsKey(kamNodeId)) {
            return supportingTermCache.get(kamNodeId);
        }

        List<BelTerm> list = new ArrayList<BelTerm>();

        ResultSet rset = null;

        try {
            PreparedStatement ps =
                    getPreparedStatement(SELECT_TERMS_IDS_BY_NODE_ID_SQL);
            ps.setInt(1, kamNodeId);

            rset = ps.executeQuery();

            while (rset.next()) {
                list.add(getBelTermById(rset.getInt(1)));
            }
        } finally {
            close(rset);
        }

        // put this list into the evidence cache
        supportingTermCache.put(kamNodeId, list);
        for (BelTerm belTerm : list) {
            supportingTermLabelReverseCache.put(belTerm.getLabel(), kamNodeId);
        }
        return list;
    }

    @SuppressWarnings("unchecked")
    @Override
    public Integer getKamNodeId(String belTerm) throws SQLException {
        if (belTerm == null) throw new NullPointerException();

        // See if the bel term is already mapped
        if (supportingTermLabelReverseCache.containsKey(belTerm)) {
            return supportingTermLabelReverseCache.get(belTerm);
        }

        // parse the BelTerm
        Term t;
        try {
            t = BELParser.parseTerm(belTerm);
        } catch (Exception e) {
            // invalid BEL
            return null;
        }

        // convert to short form
        String shortForm = t.toBELShortForm();

        // 1: short circuit; try by kam node label
        PreparedStatement ps = getPreparedStatement(SELECT_KAM_NODE_BY_LABEL_SQL);
        ResultSet rset = null;
        try {
            ps.setString(1, shortForm);
            rset = ps.executeQuery();
            if (rset.next()) {
                int kamNodeId = rset.getInt(1);
                supportingTermLabelReverseCache.put(belTerm, kamNodeId);
                return kamNodeId;
            }
        } finally {
            close(rset);
        }

        // 2: try by bel terms
        Collection<Integer> possibleTermIds = null;
        int ordinal = 0;
        for (Parameter param : t.getAllParametersLeftToRight()) {
            Integer namespaceId = null;
            if (param.getNamespace() != null
                    && StringUtils.isNotBlank(param.getNamespace().getPrefix())) {
                Namespace ns =
                        getNamespaceByPrefix(param.getNamespace().getPrefix());
                if (ns != null) {
                    namespaceId = ns.getId();
                }
            }
            String paramValue = param.getValue();
            if (paramValue.startsWith("\"") && paramValue.endsWith("\"")) {
                paramValue = paramValue.substring(1, paramValue.length() - 1);
            }

            Integer valueOid = getObjectIdByValue(paramValue);
            if (valueOid == null) {
                // could not find label for param
                if (possibleTermIds != null) {
                    possibleTermIds.clear();
                }
                break;
            }

            rset = null;
            try {
                ps = getPreparedStatement(SELECT_TERM_ID_BY_PARAMETERS_SQL);
                // set twice to handle is null, see http://stackoverflow.com/questions/4215135/
                if (namespaceId == null) {
                    ps.setNull(1, Types.INTEGER);
                    ps.setNull(2, Types.INTEGER);
                } else {
                    ps.setInt(1, namespaceId);
                    ps.setInt(2, namespaceId);
                }

                ps.setInt(3, valueOid);
                ps.setInt(4, ordinal);

                rset = ps.executeQuery();

                Set<Integer> termIdsWithParam = new HashSet<Integer>();
                while (rset.next()) {
                    termIdsWithParam.add(rset.getInt(1));
                }

                if (possibleTermIds == null) {
                    // first param
                    possibleTermIds = termIdsWithParam;
                } else {
                    possibleTermIds =
                            CollectionUtils.intersection(possibleTermIds,
                                    termIdsWithParam);
                }
            } finally {
                close(rset);
            }

            // no need to continue to next param if possibleTermIds is empty
            if (possibleTermIds.isEmpty()) {
                break;
            }
            ordinal++;
        }

        Integer kamNodeId = null;
        // iterate over all possible terms and check for label matches
        if (possibleTermIds != null) {
            for (Integer termId : possibleTermIds) {
                BelTerm term = getBelTermById(termId);
                if (term.getLabel().equals(shortForm)) {
                    kamNodeId = getKamNodeId(term);
                    break;
                }
            }
        }

        if (kamNodeId != null) {
            supportingTermLabelReverseCache.put(belTerm, kamNodeId);
        }
        return kamNodeId;
    }

    /**
     *
     * @param belTerm
     * @return
     * @throws SQLException
     */
    @Override
    public Integer getKamNodeId(BelTerm belTerm) throws SQLException {
        // See if the bel term is already mapped
        if (kamNodeTermCache.containsKey(belTerm.getId())) {
            return kamNodeTermCache.get(belTerm.getId());
        }

        ResultSet rset = null;
        Integer kamNodeId = null;
        try {
            PreparedStatement ps =
                    getPreparedStatement(SELECT_KAM_NODE_ID_BY_TERM_ID_SQL);
            ps.setInt(1, belTerm.getId());
            rset = ps.executeQuery();

            if (rset.next()) {
                kamNodeId = rset.getInt(1);
            }
        } finally {
            close(rset);
        }
        // Add to the cache
        kamNodeTermCache.put(belTerm.getId(), kamNodeId);

        return kamNodeId;
    }

    /**
     *
     */
    @Override
    public List<TermParameter> getTermParameters(BelTerm belTerm)
            throws SQLException {
        return getTermParameters(belTerm.getId());
    }

    /**
     *
     * @param belTermId
     * @return
     * @throws SQLException
     */
    private List<TermParameter> getTermParameters(Integer belTermId)
            throws SQLException {
        // See if this evidence is already cached
        if (termParameterCache.containsKey(belTermId)) {
            return termParameterCache.get(belTermId);
        }

        List<TermParameter> list = new ArrayList<TermParameter>();
        ResultSet rset = null;

        try {
            PreparedStatement ps =
                    getPreparedStatement(SELECT_TERM_PARAMETERS_BY_TERM_ID_SQL);
            ps.setInt(1, belTermId);
            rset = ps.executeQuery();

            while (rset.next()) {
                Integer termParameterId = rset.getInt(1);
                Namespace namespace = getNamespaceById(rset.getInt(2));
                String parameterValue = getObjectValueById(rset.getInt(3));
                list.add(new TermParameter(termParameterId, namespace,
                        parameterValue));
            }
        } finally {
            close(rset);
        }

        // put this list into the evidence cache
        termParameterCache.put(belTermId, list);

        return list;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Integer> getKamNodeCandidates(FunctionEnum functionType,
            Namespace namespace, String parameterValue) throws SQLException {
        // if function is null then delegate to overloaded sibling
        if (functionType == null) {
            return getKamNodeCandidates(namespace, parameterValue);
        }

        // guard against blank parameter value
        if (noLength(parameterValue)) {
            throw new InvalidArgument("parameterValue is blank");
        }

        final Integer objectId = getParameterObjectValueId(parameterValue);
        if (objectId == null) {
            // we couldn't find a string for parameterValue, so return no matches
            return new ArrayList<Integer>();
        }

        PreparedStatement ps =
                getPreparedStatement(SELECT_KAM_NODE_IDS_FOR_PARAMETER_FUNCTION_SQL);
        ps.setInt(1, functionType.getValue());

        // set namespace to search on, which may be null
        if (namespace == null || namespace.getId() == null) {
            ps.setNull(2, Types.INTEGER);
            ps.setNull(3, Types.INTEGER);
        } else {
            final int nid = namespace.getId();
            ps.setInt(2, nid);
            ps.setInt(3, nid);
        }

        ps.setInt(4, objectId);

        return queryForKamNodeCandidates(ps);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Integer> getKamNodeCandidates(Namespace namespace,
            String parameterValue) throws SQLException {
        // guard against blank parameter value
        if (noLength(parameterValue)) {
            throw new InvalidArgument("parameterValue is blank");
        }

        final Integer objectId = getParameterObjectValueId(parameterValue);
        if (objectId == null) {
            // we couldn't find a string for parameterValue, so return no matches
            return new ArrayList<Integer>();
        }

        PreparedStatement ps =
                getPreparedStatement(SELECT_KAM_NODE_IDS_FOR_PARAMETER_SQL);

        // set namespace to search on, which may be null
        if (namespace == null || namespace.getId() == null) {
            ps.setNull(1, Types.INTEGER);
            ps.setNull(2, Types.INTEGER);
        } else {
            final int nid = namespace.getId();
            ps.setInt(1, nid);
            ps.setInt(2, nid);
        }

        ps.setInt(3, objectId);

        return queryForKamNodeCandidates(ps);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Integer> getKamNodeCandidates(SkinnyUUID uuid)
            throws SQLException {
        if (uuid == null) {
            throw new InvalidArgument("uuid", uuid);
        }
        PreparedStatement ps =
                getPreparedStatement(SELECT_KAM_NODE_IDS_FOR_UUID_SQL);
        ps.setLong(1, uuid.getMostSignificantBits());
        ps.setLong(2, uuid.getLeastSignificantBits());

        return queryForKamNodeCandidates(ps);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Integer> getKamNodeCandidates(FunctionEnum functionType,
            SkinnyUUID uuid) throws SQLException {
        if (functionType == null) {
            return getKamNodeCandidates(uuid);
        }
        if (uuid == null) {
            throw new InvalidArgument("uuid", null);
        }
        PreparedStatement ps =
                getPreparedStatement(SELECT_KAM_NODE_IDS_FOR_UUID_FUNCTION_SQL);
        ps.setInt(1, functionType.getValue());
        ps.setLong(2, uuid.getMostSignificantBits());
        ps.setLong(3, uuid.getLeastSignificantBits());

        return queryForKamNodeCandidates(ps);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Integer> getKamNodeCandidates(KamNode example)
            throws SQLException {
        if (example == null || example.getId() == null) {
            throw new InvalidArgument("example", example);
        }

        List<Integer> kamNodeIds = nodeExampleMatchCache.get(example.getId());
        if (kamNodeIds != null) {
            return kamNodeIds;
        }

        PreparedStatement ps =
                getPreparedStatement(SELECT_KAM_NODES_CONTAINING_KAM_NODE_PARAMETER_SQL);
        ps.setInt(1, example.getId());
        ps.setInt(2, example.getId());

        kamNodeIds = queryForKamNodeCandidates(ps);
        nodeExampleMatchCache.put(example.getId(), kamNodeIds);

        return kamNodeIds;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map<Integer, Set<SkinnyUUID>> getKamNodeUUIDs() throws SQLException {
        PreparedStatement ps = getPreparedStatement(SELECT_KAM_NODE_UUIDS_SQL);

        Map<Integer, Set<SkinnyUUID>> uuidmap =
                new HashMap<Integer, Set<SkinnyUUID>>();
        ResultSet rset = null;
        try {
            rset = ps.executeQuery();

            while (rset.next()) {
                Integer knid = rset.getInt(1);
                Long msb = rset.getLong(2);
                Long lsb = rset.getLong(3);

                Set<SkinnyUUID> uuids = uuidmap.get(knid);
                if (uuids == null) {
                    uuids = new HashSet<SkinnyUUID>();
                    uuidmap.put(knid, uuids);
                }
                uuids.add(new SkinnyUUID(msb, lsb));
            }
        } finally {
            close(rset);
        }
        return uuidmap;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Integer getKamNodeForTerm(String term, FunctionEnum fx,
            SkinnyUUID[] u) throws SQLException {
        if (term == null) throw new InvalidArgument("term is null");
        if (fx == null) throw new InvalidArgument("fx is null");
        if (u == null) throw new InvalidArgument("u is null");
        if (u.length == 0) throw new InvalidArgument("u is empty");

        // build sql by concatenation
        String sql = SELECT_KAM_NODE_BY_TERM_UUIDS;
        String tpt = "";
        String kput = "";
        String tidj = "";
        String oj = "";
        String kuj = "";
        String uj = "";
        for (int i = 0; i < u.length; i++) {
            if (i < (u.length - 1)) {
                tpt = tpt + format($TERM_PARAMETER_TABLE, i) + ",";
                kput = kput + format($KAM_PARAMETER_UUID_TABLE, i) + ",";
                tidj = tidj + format($TERM_ID_JOIN, i) + " AND ";
                oj = oj + format($ORDINAL_JOIN, i, i) + " AND ";
                kuj = kuj + format($KAM_UUID_JOIN, i, i) + " AND ";
                uj = uj + format($UUID_JOIN, i, i) + " AND ";
            } else {
                tpt = tpt + format($TERM_PARAMETER_TABLE, i);
                kput = kput + format($KAM_PARAMETER_UUID_TABLE, i);
                tidj = tidj + format($TERM_ID_JOIN, i);
                oj = oj + format($ORDINAL_JOIN, i, i);
                kuj = kuj + format($KAM_UUID_JOIN, i, i);
                uj = uj + format($UUID_JOIN, i, i);
            }
        }

        sql = format(sql, tpt, kput, tidj, oj, kuj, uj);

        PreparedStatement ps = getPreparedStatement(sql);
        ps.setInt(1, fx.getValue());
        ps.setString(2, term);

        int index = 3;
        for (int i = 0; i < u.length; i++) {
            SkinnyUUID item = u[i];
            ps.setLong(index, item.getMostSignificantBits());
            ps.setLong(index + 1, item.getLeastSignificantBits());
            index += 2;
        }

        ResultSet rset = null;
        try {
            rset = ps.executeQuery();
            if (rset.next()) {
                int kamNodeId = rset.getInt(1);
                return kamNodeId;
            }
            return null;
        } finally {
            close(rset);
        }
    }

    private List<Integer> queryForKamNodeCandidates(final PreparedStatement ps)
            throws SQLException {
        List<Integer> kamNodeIdList = new ArrayList<Integer>();

        ResultSet rset = null;
        try {
            rset = ps.executeQuery();

            while (rset.next()) {
                Integer kamNodeId = rset.getInt(1);
                kamNodeIdList.add(kamNodeId);
            }
        } finally {
            close(rset);
        }

        return kamNodeIdList;
    }

    private Integer getParameterObjectValueId(String parameterValue)
            throws SQLException {

        // remove quoted parameter values, set to null if only ""
        if (parameterValue.startsWith("\"") && parameterValue.endsWith("\"")) {
            parameterValue = parameterValue.length() == 2 ? null :
                    parameterValue.substring(1, parameterValue.length() - 1);
        }

        final Integer objectId = getObjectIdByValue(parameterValue);
        return objectId;
    }

    /**
     *
     * @param belTermId
     * @return
     * @throws SQLException
     */
    private BelTerm getBelTermById(Integer belTermId) throws SQLException {

        // See if the term is cached
        if (termCache.containsKey(belTermId)) {
            return termCache.get(belTermId);
        }

        BelTerm belTerm = null;
        ResultSet rset = null;

        try {
            PreparedStatement ps = getPreparedStatement(SELECT_TERM_BY_ID_SQL);
            ps.setInt(1, belTermId);

            rset = ps.executeQuery();

            if (rset.next()) {
                String label = getObjectValueById(rset.getInt(1));
                // Get the term parameters for this term and reconstruct the
                // label based on its original encoding
                List<TermParameter> tparams = getTermParameters(belTermId);
                for (TermParameter termParameter : tparams) {
                    String nsprefix = termParameter.namespace != null ?
                            termParameter.namespace.prefix : null;

                    // quote parameter if necessary
                    String paramValue = termParameter.parameterValue;
                    paramValue = quoteParameter(paramValue);

                    final String termParam;
                    if (nsprefix != null) {
                        termParam = nsprefix + ":" + paramValue;
                    } else {
                        termParam = paramValue;
                    }
                    label = label.replaceFirst("#", termParam);
                }
                belTerm = new BelTerm(belTermId, label);
            }
        } finally {
            close(rset);
        }

        // Insert this term into the cache
        termCache.put(belTermId, belTerm);

        return belTerm;
    }

    /**
     *
     * @param namespaceId
     * @return
     * @throws SQLException
     */
    private Namespace getNamespaceById(Integer namespaceId) throws SQLException {

        // See if the term is cached
        if (namespaceCache.containsKey(namespaceId)) {
            return namespaceCache.get(namespaceId);
        }

        Namespace namespace = null;
        ResultSet rset = null;

        try {
            PreparedStatement ps =
                    getPreparedStatement(SELECT_NAMESPACE_BY_ID_SQL);
            ps.setInt(1, namespaceId);

            rset = ps.executeQuery();

            if (rset.next()) {
                namespace = getNamespace(rset);
            }
        } finally {
            close(rset);
        }

        // Insert this term into the cache
        if (namespace != null) {
            namespaceCache.put(namespaceId, namespace);
            namespacePrefixCache.put(namespace.getPrefix(), namespace);
        }

        return namespace;

    }

    /**
     *
     * @param belDocumentId
     * @return
     * @throws SQLException
     */
    private BelDocumentInfo getBelDocumentInfoById(Integer belDocumentId)
            throws SQLException {

        // See if the document is cached
        if (documentCache.containsKey(belDocumentId)) {
            return documentCache.get(belDocumentId);
        }

        ResultSet rset = null;
        BelDocumentInfo belDocumentInfo = null;

        try {
            PreparedStatement ps =
                    getPreparedStatement(SELECT_DOCUMENT_BY_ID_SQL);
            ps.setInt(1, belDocumentId);

            rset = ps.executeQuery();

            if (rset.next()) {
                belDocumentInfo = getBelDocumentInfo(rset);
            }
        } finally {
            close(rset);
        }

        // Insert this document into the cache
        documentCache.put(belDocumentId, belDocumentInfo);

        return belDocumentInfo;
    }

    /**
     *
     * @param belDocumentId
     * @return
     * @throws SQLException
     */
    private List<Namespace> getNamespacesByDocumentId(Integer belDocumentId)
            throws SQLException {

        ResultSet rset = null;
        List<Namespace> list = new ArrayList<Namespace>();

        try {
            PreparedStatement ps =
                    getPreparedStatement(SELECT_NAMESPACES_BY_DOCUMENT_ID_SQL);
            ps.setInt(1, belDocumentId);

            rset = ps.executeQuery();

            while (rset.next()) {
                Integer namespaceId = rset.getInt(1);
                list.add(getNamespaceById(namespaceId));
            }
        } finally {
            close(rset);
        }

        return list;
    }

    /**
     * Obtain a namespace by prefix
     * @param prefix
     * @return
     * @throws SQLException
     */
    private Namespace getNamespaceByPrefix(String prefix) throws SQLException {
        if (namespacePrefixCache.containsKey(prefix)) {
            return namespacePrefixCache.get(prefix);
        }

        ResultSet rset = null;
        Namespace ns = null;

        try {
            PreparedStatement ps =
                    getPreparedStatement(SELECT_NAMESPACE_BY_PREFIX_SQL);
            ps.setString(1, prefix);

            rset = ps.executeQuery();

            if (rset.next()) {
                ns = getNamespace(rset);
            }
        } finally {
            close(rset);
        }

        if (ns != null) {
            namespacePrefixCache.put(prefix, ns);
            namespaceCache.put(ns.getId(), ns);
        }

        return ns;
    }

    /**
     *
     * @param belDocumentId
     * @return
     * @throws SQLException
     */
    private List<AnnotationType> getAnnotationTypesByDocumentId(
            Integer belDocumentId) throws SQLException {
        ResultSet rset = null;
        List<AnnotationType> list = new ArrayList<AnnotationType>();

        try {
            PreparedStatement ps =
                    getPreparedStatement(SELECT_ANNOTATION_TYPES_BY_DOCUMENT_ID_SQL);
            ps.setInt(1, belDocumentId);

            rset = ps.executeQuery();

            while (rset.next()) {
                Integer annotationTypeId = rset.getInt(1);
                AnnotationType type = getAnnotationTypeById(annotationTypeId);

                addAnnotationType(list, type);
            }
        } finally {
            close(rset);
        }

        return list;
    }

    /**
     * Adds the {@link AnnotationType type} to the {@link List list} based on:
     * <ul>
     * <li>if {@link CitationNameAnnotationDefinition#ANNOTATION_DEFINITION_ID}
     * is the annotation then add a Citation {@link AnnotationType annotation type}</li>
     * <li>if another Citation field is the annotation then ignore it</li>
     * <li>any other annotation is added</li>
     * </ul>
     *
     * @param list the {@link List annotation type list}
     * @param type the {@link AnnotationType annotation type} to add
     */
    private void addAnnotationType(List<AnnotationType> list,
            AnnotationType type) {
        boolean isCitation = citationTypes.contains(type.getName());
        if (isCitation) {
            // add Citation is we have seen a CitationName annotation, throw away the others
            boolean isCitationName =
                    CitationNameAnnotationDefinition.ANNOTATION_DEFINITION_ID
                            .equals(type.getName());
            if (isCitationName) {
                list.add(createCitationType(type));
            }
        } else {
            list.add(type);
        }
    }

    /**
     *
     * @param statementId
     * @return
     * @throws SQLException
     */
    private List<Annotation> getAnnotationsByStatementId(Integer statementId)
            throws SQLException {

        // See if the annotation set for this statement is already cached
        if (statementAnnotationCache.containsKey(statementId)) {
            return statementAnnotationCache.get(statementId);
        }

        ResultSet rset = null;
        List<Annotation> list = new ArrayList<Annotation>();

        try {
            PreparedStatement ps =
                    getPreparedStatement(SELECT_ANNOTATIONS_BY_STATEMENT_ID_SQL);
            ps.setInt(1, statementId);

            rset = ps.executeQuery();

            while (rset.next()) {
                Integer annotationId = rset.getInt(1);
                list.add(getAnnotation(annotationId));
            }
        } finally {
            close(rset);
        }

        // Add this to the cache
        statementAnnotationCache.put(statementId, list);

        return list;
    }

    /**
     * Builds an SQL snippet to help select KAM edges that match <code>kamFilter</code>.
     *
     * The returned SQL snippet is a SELECT statement that queries the {@code kam_edge_id}'s of
     * the KAM edges that satisfy the provided KAM filter.  Callers may INNER JOIN with the snippet
     * to select more fields of the {@code kam_edge} table.
     *
     * @param kamFilter A selection of KAM filter criteria to apply in including or excluding edges.
     * <code>kamFilter</code> can not be null.
     * @return A pair of a SQL snippet and a list of all parameters to bind when using the
     * generated SQL in a PreparedStatement.
     */
    private Pair<String, List<String>> getFilteredSelectProtoEdgesSql(
            KamFilter kamFilter) {

        List<FilterCriteria> criteria = kamFilter.getFilterCriteria();

        // Create a StringBuilder for each part of the SQL query.
        StringBuilder baseQuery =
                new StringBuilder("SELECT ke.kam_edge_id FROM @.kam_edge ke");
        StringBuilder citationJoins = new StringBuilder();
        StringBuilder whereClause = new StringBuilder(" WHERE TRUE");
        StringBuilder havingClause = new StringBuilder();

        boolean joinedStatements = false, joinedAnnotations = false, groupedByEdge =
                false;

        // Create lists to contain the Strings that will need to
        // be bound in the PreparedStatement
        ArrayList<String> annotationParameters = new ArrayList<String>(), citationParameters =
                new ArrayList<String>();

        int uniqueSubselectId = 0;

        // Matching some of the types of FilterCriteria require an SQL aggregate function
        // that computes boolean AND or OR over a group.
        // Use MAX(CASE WHEN some_boolean THEN 1 ELSE 0 END)=1 for a boolean OR aggregate function.
        // Use MIN(CASE WHEN some_boolean THEN 1 ELSE 0 END)=1 for a boolean AND aggregate function.

        for (FilterCriteria criterion : criteria) {
            boolean include = criterion.isInclude();

            // The filter criteria are ANDed together.

            if (criterion instanceof RelationshipTypeFilterCriteria) {
                Set<RelationshipType> relationships =
                        ((RelationshipTypeFilterCriteria) criterion)
                                .getValues();
                int size = relationships.size();
                if (size == 0) {
                    // There is nothing on which to match.
                    continue;
                }

                whereClause.append(" AND ");

                // An include filter matches the edges that have at least one of the provided
                // relationship types (i.e. the tests of equality of the edge relationship type to each
                // of the provided relationship types are ORed).
                // An exclude filter matches the edges that have none of the provided relationship types
                // (i.e. the tests of equality of the edge relationship type to each of the provided
                // relationship types are ORed, then finally complemented (NOT)).
                if (!include) {
                    whereClause.append("NOT ");
                }

                whereClause.append("(");
                int count = 0;
                for (RelationshipType relationship : relationships) {
                    whereClause.append("ke.relationship_type_id=");
                    whereClause.append(relationship.getValue());
                    if (++count < size) {
                        whereClause.append(" OR ");
                    }
                }
                whereClause.append(")");

            } else if (criterion instanceof BelDocumentFilterCriteria) {
                Set<BelDocumentInfo> documents =
                        ((BelDocumentFilterCriteria) criterion).getValues();
                int size = documents.size();
                if (size == 0) {
                    // There is nothing on which to match.
                    continue;
                }

                // The provided documents are matched against the documents associated with the
                // supporting evidence for the KAM edges, that is the statements that are associated
                // with the edge.   This filter requires joining with the <code>statement</code> table.
                if (!joinedStatements) {
                    baseQuery
                            .append(" LEFT OUTER JOIN @.kam_edge_statement_map kesm ON ke.kam_edge_id=kesm.kam_edge_id");
                    baseQuery
                            .append(" LEFT OUTER JOIN @.statement s ON kesm.statement_id=s.statement_id");
                    joinedStatements = true;
                }

                // An include filter matches only the edges that have supporting evidence statements that
                // have a document that is one of the provided documents.  A predicate expression
                // for "being one of the provided documents" must be evaluated for all documents
                // of supporting statements.  If that predicate is true for any document then the edge
                // passes the filter (i.e. a boolean OR).
                // An exclude filter matches only the edges all of whose supporting evidence statements do
                // not have documents that are one of the provided documents.  The predicate expression
                // of "being one of the provided documents" must be false for all documents of all
                // supporting statements for an edge to pass an "exclude" filter (i.e. NOT OR).

                // The query will need to group by edge and use a HAVING clause to match the predicate.
                if (!groupedByEdge) {
                    havingClause = new StringBuilder(" HAVING TRUE");
                    groupedByEdge = true;
                }

                havingClause.append(" AND");
                if (!include) {
                    havingClause.append(" NOT");
                }
                havingClause.append(" MAX(CASE WHEN (");
                int count = 0;
                for (BelDocumentInfo doc : documents) {
                    havingClause.append("s.document_id=");
                    havingClause.append(doc.getId());
                    if (++count < size) {
                        havingClause.append(" OR ");
                    }
                }
                havingClause.append(") THEN 1 ELSE 0 END)=1");

            } else if (criterion instanceof AnnotationFilterCriteria) {
                AnnotationFilterCriteria ac =
                        (AnnotationFilterCriteria) criterion;
                Integer type = ac.getAnnotationType().getId();
                Set<String> annotations = ac.getValues();
                int size = annotations.size();
                if (size == 0) {
                    continue;
                }

                // This case is very similar to the BelDocumentFilterCriteria case, except
                // that the match is performed on non-citation annotations.

                // The query will need to join on the <code>statement</code> table.
                if (!joinedStatements) {
                    baseQuery
                            .append(" LEFT OUTER JOIN @.kam_edge_statement_map kesm ON ke.kam_edge_id=kesm.kam_edge_id");
                    baseQuery
                            .append(" LEFT OUTER JOIN @.statement s ON kesm.statement_id=s.statement_id");
                    joinedStatements = true;
                }

                // The query will need to group by edge.
                if (!groupedByEdge) {
                    havingClause = new StringBuilder(" HAVING TRUE");
                    groupedByEdge = true;
                }

                // The query will also need to join the <code>annotations</code> table.
                if (!joinedAnnotations) {
                    baseQuery
                            .append(" LEFT OUTER JOIN @.statement_annotation_map sam ON s.statement_id=sam.statement_id");
                    baseQuery
                            .append(" LEFT OUTER JOIN @.annotation a ON sam.annotation_id=a.annotation_id");
                    baseQuery
                            .append(" LEFT OUTER JOIN @.annotation_definition ad ON a.annotation_definition_id=ad.annotation_definition_id");
                    baseQuery
                            .append(" LEFT OUTER JOIN @.objects o ON a.value_oid=o.objects_id");
                    baseQuery
                            .append(" LEFT OUTER JOIN @.objects_text ot ON o.objects_text_id=ot.objects_text_id");
                    joinedAnnotations = true;
                }

                havingClause.append(" AND");
                if (!include) {
                    havingClause.append(" NOT");
                }
                havingClause.append(" MAX(CASE WHEN (");
                int count = 0;
                for (String annotation : annotations) {
                    annotationParameters.add(annotation);
                    havingClause.append("(a.annotation_definition_id=");
                    havingClause.append(type);
                    havingClause
                            .append(" AND ((o.varchar_value IS NOT NULL AND o.varchar_value=?)");
                    havingClause.append(" OR (o.varchar_value IS NULL AND ");
                    if (dbConnection.isDerby()) {
                        // Apache Derby does not support comparing CLOBs so cast
                        // to the largest VARCHAR type for the comparison.
                        havingClause
                                .append("CAST(ot.text_value AS VARCHAR(32672))");
                    } else {
                        havingClause.append("ot.text_value");
                    }
                    havingClause.append("=?)))");

                    if (++count < size) {
                        havingClause.append(" OR ");
                    }
                }
                havingClause.append(") THEN 1 ELSE 0 END)=1");

            } else if (criterion instanceof CitationFilterCriteria) {
                Set<Citation> citations =
                        ((CitationFilterCriteria) criterion).getValues();
                int size = citations.size();
                if (size == 0) {
                    continue;
                }

                // This case is similar to the AnnotationFilterCriteria, except that the annotations
                // used in the match are the predefined citation annotations
                // (authors, comment, date, name, reference, and type) and the predicate
                // to determine whether the citation of a supporting evidence statement
                // matches a provided citation is computed in a subselect.

                // The query will need to join on the <code>statement</code> table.
                if (!joinedStatements) {
                    baseQuery
                            .append(" LEFT OUTER JOIN @.kam_edge_statement_map kesm ON ke.kam_edge_id=kesm.kam_edge_id");
                    baseQuery
                            .append(" LEFT OUTER JOIN @.statement s ON kesm.statement_id=s.statement_id");
                    joinedStatements = true;
                }

                whereClause.append(" AND");

                // An include filter matches an edge only if one of the supporting statements has one
                // of the provided citations (i.e. boolean OR).
                // An exclude filter matches an edge only if none of the supporting statements has one
                // of the provided citations (i.e. NOT OR).
                if (!include) {
                    whereClause.append(" NOT");
                }
                whereClause.append(" (FALSE");

                for (Citation citation : citations) {
                    final String id = citation.getId();
                    final String name = citation.getName();
                    final String comment = citation.getComment();
                    final CitationType citationType =
                            citation.getCitationType();
                    final String ctype = citationType != null ? citationType
                            .getDisplayValue() : null;

                    // Pack the authors string exactly as done in
                    // CitationDataConverter.convert(Citation, Map<String, BELAnnotationDefinition>).
                    String author = null;
                    final List<String> authors = citation.getAuthors();
                    if (BELUtilities.hasItems(authors)) {
                        author = StringUtils.left(PackUtils.packValues(authors), 4000);
                    }

                    final Date date = citation.getPublicationDate();
                    final String publicationDate = (date != null ? dateFormat
                            .format(date) : null);

                    citationJoins.append(" LEFT OUTER JOIN (");
                    citationJoins
                            .append("SELECT s.statement_id statement_id, SUM(CASE WHEN (FALSE");

                    int countNulls = 0;
                    for (String type : KAMStoreConstants.CITATION_ANNOTATION_DEFINITION_IDS) {
                        String value = null;
                        if (type == CitationAuthorsAnnotationDefinition.ANNOTATION_DEFINITION_ID) {
                            value = author;
                        } else if (type == CitationDateAnnotationDefinition.ANNOTATION_DEFINITION_ID) {
                            value = publicationDate;
                        } else if (type == CitationNameAnnotationDefinition.ANNOTATION_DEFINITION_ID) {
                            value = name;
                        } else if (type == CitationTypeAnnotationDefinition.ANNOTATION_DEFINITION_ID) {
                            value = ctype;
                        } else if (type == CitationCommentAnnotationDefinition.ANNOTATION_DEFINITION_ID) {
                            value = comment;
                        } else if (type == CitationReferenceAnnotationDefinition.ANNOTATION_DEFINITION_ID) {
                            value = id;
                        }

                        if (noLength(value)) {
                            // For example the citation date can be null, in which case the
                            // supporting statements are not checked for the citation date
                            // annotation.
                            ++countNulls;
                            continue;
                        }

                        citationParameters.add(value);

                        citationJoins.append(" OR (ad.name='");
                        citationJoins.append(type);
                        citationJoins
                                .append("' AND ((o.varchar_value IS NOT NULL AND o.varchar_value=?)");
                        citationJoins
                                .append(" OR (o.varchar_value IS NULL AND ");
                        if (dbConnection.isDerby()) {
                            // Apache Derby does not support comparing CLOBs so cast
                            // to the largest VARCHAR type for the comparison.
                            // https://db.apache.org/derby/docs/10.7/ref/rrefjdbc96386.html
                            citationJoins
                                    .append("CAST(ot.text_value AS VARCHAR(32672))");
                        } else {
                            citationJoins.append("ot.text_value");
                        }
                        citationJoins.append("=?)))");
                    }

                    citationJoins.append(") THEN 1 ELSE 0 END) citations");
                    citationJoins.append(" FROM @.statement s");
                    citationJoins
                            .append(" LEFT OUTER JOIN @.statement_annotation_map sam ON s.statement_id=sam.statement_id");
                    citationJoins
                            .append(" LEFT OUTER JOIN @.annotation a ON sam.annotation_id=a.annotation_id");
                    citationJoins
                            .append(" LEFT OUTER JOIN @.annotation_definition ad ON a.annotation_definition_id=ad.annotation_definition_id");
                    citationJoins
                            .append(" LEFT OUTER JOIN @.objects o ON a.value_oid=o.objects_id");
                    citationJoins
                            .append(" LEFT OUTER JOIN @.objects_text ot ON o.objects_text_id=ot.objects_text_id");
                    citationJoins.append(" GROUP BY s.statement_id) t");
                    citationJoins.append(uniqueSubselectId);
                    citationJoins.append(" ON s.statement_id=t");
                    citationJoins.append(uniqueSubselectId);
                    citationJoins.append(".statement_id");

                    // The subselect above stores whether the supporting statements of each edge have the
                    // provided citation in the "has_citation" field.
                    whereClause.append(" OR (t");
                    whereClause.append(uniqueSubselectId);
                    whereClause.append(".citations<>0 AND MOD(t");
                    whereClause.append(uniqueSubselectId);
                    whereClause.append(".citations,");
                    whereClause
                            .append(KAMStoreConstants.CITATION_ANNOTATION_DEFINITION_IDS.length
                                    - countNulls);
                    whereClause.append(")=0)");

                    ++uniqueSubselectId;
                }

                whereClause.append(")");
            }
        }

        // Prepare the final SQL query.
        baseQuery.append(citationJoins);
        baseQuery.append(whereClause);
        if (groupedByEdge) {
            baseQuery.append(" GROUP BY ke.kam_edge_id");
            baseQuery.append(havingClause);
        }

        // Create a pair of the SQL query string and a list of the string parameters that
        // need to be bound, in order, in any PreparedStatement that uses the query string.
        final String sql = baseQuery.toString();
        final int size =
                2 * (citationParameters.size() + annotationParameters.size());
        final List<String> bindings = new ArrayList<String>(size);
        for (String param : citationParameters) {
            bindings.add(param);
            bindings.add(param);
        }
        for (String param : annotationParameters) {
            bindings.add(param);
            bindings.add(param);
        }

        return new Pair<String, List<String>>(sql, bindings);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public AllocatingIterator<SimpleKAMNode> iterateNodes() throws SQLException {
        PreparedStatement ps1, ps2;
        ps1 = getPreparedStatement(SELECT_PROTO_NODES_SQL);
        ps2 = getPreparedStatement(SELECT_KAM_NODE_PARAMETERS_PREFIX_SQL
                + SELECT_KAM_NODE_PARAMETERS_ORDER_SQL,
                TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY);

        final ResultSet nodeRS = ps1.executeQuery();
        final ResultSet paramRS = ps2.executeQuery();

        class Iter implements AllocatingIterator<SimpleKAMNode> {
            private boolean closed = false;

            @Override
            public boolean hasNext() {
                if (closed) throw new IllegalStateException("closed");
                try {
                    return nodeRS.next();
                } catch (SQLException e) {
                    e.printStackTrace();
                    close();
                    return false;
                }
            }

            @Override
            public SimpleKAMNode next() {
                if (closed) throw new IllegalStateException("closed");
                KamProtoNode kpn;
                try {
                    kpn = getKamProtoNode(nodeRS, paramRS);
                } catch (SQLException e) {
                    e.printStackTrace();
                    close();
                    return null;
                }
                int id = kpn.getId();
                FunctionEnum fx = kpn.getFunctionType();
                String lbl = kpn.getLabel();
                _KamNode node = new _KamNode(id, fx, lbl);
                return node;
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }

            @Override
            public void close() {
                closed = true;
                try {
                    nodeRS.close();
                    paramRS.close();
                } catch (SQLException e) {
                    // ignore it
                }
            }

            @Override
            public void finalize() {
                close();
            }

        }
        return new Iter();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public AllocatingIterator<SimpleKAMEdge> iterateEdges() throws SQLException {
        PreparedStatement ps = getPreparedStatement(SELECT_PROTO_EDGES_SQL);
        final ResultSet edgeRS = ps.executeQuery();
        class Iter implements AllocatingIterator<SimpleKAMEdge> {
            private boolean closed = false;

            @Override
            public boolean hasNext() {
                if (closed) throw new IllegalStateException("closed");
                try {
                    return edgeRS.next();
                } catch (SQLException e) {
                    e.printStackTrace();
                    close();
                    return false;
                }
            }

            @Override
            public SimpleKAMEdge next() {
                if (closed) throw new IllegalStateException("closed");
                int id, rel, src, tgt;
                try {
                    id = edgeRS.getInt(1);
                    src = edgeRS.getInt(2);
                    rel = edgeRS.getInt(3);
                    tgt = edgeRS.getInt(4);
                } catch (SQLException e) {
                    e.printStackTrace();
                    close();
                    return null;
                }
                return new _KamEdge(id, fromValue(rel), src, tgt);
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }

            @Override
            public void close() {
                closed = true;
                try {
                    edgeRS.close();
                } catch (SQLException e) {
                    // ignore it
                }
            }

            @Override
            public void finalize() {
                close();
            }

        }
        return new Iter();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int countNodes() throws SQLException {
        PreparedStatement ps = getPreparedStatement(SELECT_COUNT_NODES);
        ResultSet rs = ps.executeQuery();
        if (rs.next()) return rs.getInt(1);
        return 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int countEdges() throws SQLException {
        PreparedStatement ps = getPreparedStatement(SELECT_COUNT_EDGES);
        ResultSet rs = ps.executeQuery();
        if (rs.next()) return rs.getInt(1);
        return 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public KamProtoNodesAndEdges getKamProtoNodesAndEdges(KamInfo kamInfo)
            throws SQLException {

        Map<Integer, KamProtoNode> nodes = new HashMap<Integer, KamProtoNode>();
        Map<Integer, KamProtoEdge> edges = new HashMap<Integer, KamProtoEdge>();
        ResultSet nodesRs = null;
        ResultSet paramsRs = null;

        try {
            PreparedStatement nps =
                    getPreparedStatement(SELECT_PROTO_NODES_SQL);
            nodesRs = nps.executeQuery();

            PreparedStatement pps = getPreparedStatement(
                    SELECT_KAM_NODE_PARAMETERS_PREFIX_SQL
                            + SELECT_KAM_NODE_PARAMETERS_ORDER_SQL,
                    TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY);
            paramsRs = pps.executeQuery();

            while (nodesRs.next()) {
                KamProtoNode kamProtoNode = getKamProtoNode(nodesRs, paramsRs);
                nodes.put(kamProtoNode.getId(), kamProtoNode);
            }
        } finally {
            close(nodesRs);
            close(paramsRs);
        }

        try {
            PreparedStatement ps = getPreparedStatement(SELECT_PROTO_EDGES_SQL);
            nodesRs = ps.executeQuery();

            while (nodesRs.next()) {
                Integer kamEdgeId = nodesRs.getInt(1);
                KamProtoNode sourceKamProtoNode = nodes.get(nodesRs.getInt(2));
                Integer relationshipTypeId = nodesRs.getInt(3);
                KamProtoNode targetKamProtoNode = nodes.get(nodesRs.getInt(4));

                // Sanity checks
                if (null == sourceKamProtoNode) {
                    throw new SQLException(String.format(
                            "Source node for edge %d is missing.", kamEdgeId));
                }
                if (null == targetKamProtoNode) {
                    throw new SQLException(String.format(
                            "Target node for edge %d is missing.", kamEdgeId));
                }
                edges.put(kamEdgeId,
                        new KamProtoEdge(kamEdgeId, sourceKamProtoNode,
                                fromValue(relationshipTypeId),
                                targetKamProtoNode));
            }
        } finally {
            close(nodesRs);
            close(paramsRs);
        }

        return new KamProtoNodesAndEdges(nodes, edges);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public KamProtoNodesAndEdges getKamProtoNodesAndEdges(KamInfo kamInfo,
            KamFilter kamFilter) throws SQLException {

        if (kamFilter == null) {
            // The result will be the same as the result of continuing in this method,
            // but getKamProtoNodesAndEdges(KamInfo) can better handle the query.
            return getKamProtoNodesAndEdges(kamInfo);
        }

        Map<Integer, KamProtoNode> nodes = new HashMap<Integer, KamProtoNode>();
        Map<Integer, KamProtoEdge> edges = new HashMap<Integer, KamProtoEdge>();
        ResultSet nodesRs = null;
        ResultSet paramsRs = null;

        // Get an SQL snippet to help query the KAM nodes and edges.
        final Pair<String, List<String>> pair =
                getFilteredSelectProtoEdgesSql(kamFilter);
        final String snippet = pair.getFirst();
        final List<String> bindings = pair.getSecond();

        // Query the KAM nodes that are either a source or target node of
        // a KAM edge that satisfies the KAM filter.
        StringBuilder nodesSql = new StringBuilder(SELECT_PROTO_NODES_SQL);
        nodesSql.append(
                " INNER JOIN (SELECT ke.kam_source_node_id, ke.kam_target_node_id FROM @.kam_edge ke INNER JOIN (")
                .append(snippet)
                .append(") fke ON ke.kam_edge_id=fke.kam_edge_id")
                .append(") ke ON kn.kam_node_id=ke.kam_source_node_id OR kn.kam_node_id=ke.kam_target_node_id");

        // Same for global parameters
        StringBuilder paramSql =
                new StringBuilder(SELECT_KAM_NODE_PARAMETERS_PREFIX_SQL)
                        .append(" INNER JOIN (SELECT ke.kam_source_node_id, ke.kam_target_node_id FROM @.kam_edge ke INNER JOIN (")
                        .append(snippet)
                        .append(") fke ON ke.kam_edge_id=fke.kam_edge_id")
                        .append(") ke ON knp.kam_node_id=ke.kam_source_node_id OR knp.kam_node_id=ke.kam_target_node_id")
                        .append(SELECT_KAM_NODE_PARAMETERS_ORDER_SQL);

        try {
            PreparedStatement nps = getPreparedStatement(nodesSql.toString());
            int count = 0;
            for (String param : bindings) {
                nps.setString(++count, param);
            }
            nodesRs = nps.executeQuery();

            PreparedStatement pps = getPreparedStatement(paramSql.toString(),
                    TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY);
            count = 0;
            for (String param : bindings) {
                pps.setString(++count, param);
            }
            paramsRs = pps.executeQuery();

            while (nodesRs.next()) {
                KamProtoNode kamProtoNode = getKamProtoNode(nodesRs, paramsRs);
                nodes.put(kamProtoNode.getId(), kamProtoNode);
            }
        } finally {
            close(nodesRs);
        }

        // Then query the KAM edges.
        StringBuilder sql2 = new StringBuilder(SELECT_PROTO_EDGES_SQL);
        sql2.append(" INNER JOIN (")
                .append(snippet)
                .append(") fke ON ke.kam_edge_id=fke.kam_edge_id");
        try {
            PreparedStatement ps = getPreparedStatement(sql2.toString());
            int count = 0;
            for (String param : bindings) {
                ps.setString(++count, param);
            }
            nodesRs = ps.executeQuery();

            while (nodesRs.next()) {
                Integer kamEdgeId = nodesRs.getInt(1);
                KamProtoNode sourceKamProtoNode = nodes.get(nodesRs.getInt(2));
                Integer relationshipTypeId = nodesRs.getInt(3);
                KamProtoNode targetKamProtoNode = nodes.get(nodesRs.getInt(4));

                // Sanity checks
                if (null == sourceKamProtoNode) {
                    throw new SQLException(String.format(
                            "Source node for edge %d is missing.", kamEdgeId));
                }
                if (null == targetKamProtoNode) {
                    throw new SQLException(String.format(
                            "Target node for edge %d is missing.", kamEdgeId));
                }
                edges.put(kamEdgeId,
                        new KamProtoEdge(kamEdgeId, sourceKamProtoNode,
                                fromValue(relationshipTypeId),
                                targetKamProtoNode));
            }
        } finally {
            close(nodesRs);
        }

        return new KamProtoNodesAndEdges(nodes, edges);
    }

    /**
     *
     * @return
     * @throws SQLException
     */
    protected Collection<Citation> getAllCitations() throws SQLException {

        ResultSet rset = null;
        Map<String, Citation> citations = new HashMap<String, Citation>();

        try {
            PreparedStatement ps =
                    getPreparedStatement(SELECT_CITATION_ANNOTATIONS_SQL);
            rset = ps.executeQuery();
            List<Annotation> annotations = new ArrayList<Annotation>();
            Integer lastStatementId = null;
            //annotations ordered by statement ID
            while (rset.next()) {
                Integer statementId = rset.getInt(4);
                //Integer documentId = rset.getInt(5);
                if (lastStatementId == null) {
                    lastStatementId = statementId;
                }
                if (!lastStatementId.equals(statementId)) {
                    //new citation
                    Citation citation = new Citation(annotations);
                    String key = makeCitationKey(citation);
                    if (!citations.containsKey(key)) {
                        citations.put(key, citation);
                    }
                    annotations.clear();
                }
                annotations.add(getAnnotation(rset));
                lastStatementId = statementId;
            }
            Citation citation = new Citation(annotations);
            String key = makeCitationKey(citation);
            citations.put(key, citation);
        } finally {
            close(rset);
        }

        return citations.values();
    }

    //temporary key for determining citation equivalence
    private String makeCitationKey(Citation citation) {
        return citation.getName() + "," + citation.getId() + ","
                + citation.getCitationType();
    }

    private synchronized void initializeCitationMaps() throws SQLException {
        if (belDocumentCitationsMap == null || citationMap == null) {
            belDocumentCitationsMap = new HashMap<Integer, List<Citation>>();
            citationMap = new HashMap<String, Citation>();

            ResultSet rset = null;
            Map<String, Citation> citations = new HashMap<String, Citation>();

            try {
                PreparedStatement ps =
                        getPreparedStatement(SELECT_CITATION_ANNOTATIONS_SQL);
                rset = ps.executeQuery();
                List<Annotation> annotations = new ArrayList<Annotation>();
                Integer lastStatementId = 1;
                Integer lastDocumentId = null;
                //annotations ordered by statement ID
                while (rset.next()) {
                    Integer annotationId = rset.getInt(1);
                    Integer statementId = rset.getInt(2);
                    Integer documentId = rset.getInt(3);

                    if (!lastStatementId.equals(statementId)) {
                        //new citation
                        Citation citation = new Citation(annotations);
                        String key = makeCitationKey(citation);
                        if (!citations.containsKey(key)) {
                            citations.put(key, citation);
                            processCitationMapEntry(citation, documentId);
                        }
                        annotations.clear();
                    }
                    annotations.add(getAnnotation(annotationId));
                    lastStatementId = statementId;
                    lastDocumentId = documentId;
                }
                Citation citation = new Citation(annotations);
                String key = makeCitationKey(citation);
                if (!citations.containsKey(key)) {
                    citations.put(key, citation);
                    processCitationMapEntry(citation, lastDocumentId);
                }

            } finally {
                close(rset);
            }
        }
    }

    private void processCitationMapEntry(Citation citation, Integer documentId) {
        citationMap.put(citation.getId(), citation);
        List<Citation> documentCitations =
                belDocumentCitationsMap.get(documentId);
        if (documentCitations == null) {
            documentCitations = new ArrayList<Citation>();
            belDocumentCitationsMap.put(documentId, documentCitations);
        }
        documentCitations.add(citation);
    }

    @Override
    public List<Citation> getCitations(BelDocumentInfo belDocumentInfo)
            throws SQLException {
        List<Citation> citations = new ArrayList<Citation>();
        if (belDocumentCitationsMap == null) {
            initializeCitationMaps();
        }
        List<Citation> matches =
                belDocumentCitationsMap.get(belDocumentInfo.getId());
        if (matches != null) {
            citations.addAll(matches);
        }
        return citations;
    }

    @Override
    public List<Citation> getCitations(BelDocumentInfo belDocumentInfo,
            CitationType citationType) throws SQLException {
        List<Citation> citations = new ArrayList<Citation>();
        for (Citation citation : getCitations(belDocumentInfo)) {
            if (citation.getCitationType() == citationType) {
                citations.add(citation);
            }
        }
        return citations;
    }

    @Override
    public List<Citation> getCitations(CitationType citationType)
            throws SQLException {
        if (citationMap == null) {
            initializeCitationMaps();
        }
        List<Citation> citations = new ArrayList<Citation>();
        for (Citation citation : citationMap.values()) {
            if (citation.getCitationType() == citationType) {
                citations.add(citation);
            }
        }
        return citations;
    }

    @Override
    public List<Citation> getCitations(CitationType citationType,
            String... referenceIds) throws SQLException {
        if (referenceIds == null) {
            return emptyList();
        }
        if (citationMap == null) {
            initializeCitationMaps();
        }
        List<Citation> citations = new ArrayList<Citation>();
        for (int i = 0; i < referenceIds.length; i++) {
            Citation citation = citationMap.get(referenceIds[i]);
            if (citation != null && citation.getCitationType() == citationType) {
                citations.add(citation);
            }
        }
        return citations;
    }

    /**
     *
     * @param annotationTypeId
     * @return
     * @throws SQLException
     */
    private AnnotationType getAnnotationTypeById(Integer annotationTypeId)
            throws SQLException {
        // See if the term is cached
        if (annotationTypeCache.containsKey(annotationTypeId)) {
            return annotationTypeCache.get(annotationTypeId);
        }

        AnnotationType annotationType = null;
        ResultSet rset = null;

        try {
            PreparedStatement ps =
                    getPreparedStatement(SELECT_ANNOTATION_TYPE_BY_ID_SQL);
            ps.setInt(1, annotationTypeId);
            rset = ps.executeQuery();

            if (rset.next()) {
                annotationType = getAnnotationType(rset);
            }
        } finally {
            close(rset);
        }

        annotationTypeCache.put(annotationTypeId, annotationType);

        return annotationType;
    }

    @Override
    public List<String> getAnnotationTypeDomainValues(
            AnnotationType annotationType) throws SQLException,
            ExternalResourceException {
        if (annotationType == null) {
            throw new InvalidArgument("annotationType", annotationType);
        }

        if (CITATION.equals(annotationType.getName())) {
            return Arrays.asList(".*");
        }

        if (annotationTypeValueCache.containsKey(annotationType.getId())) {
            return annotationTypeValueCache.get(annotationType.getId());
        }

        List<String> domainValues = new ArrayList<String>();
        ResultSet rset = null;

        try {
            PreparedStatement ps =
                    getPreparedStatement(SELECT_ANNOTATION_TYPE_DOMAIN_VALUE_SQL);
            ps.setInt(1, annotationType.getId());
            rset = ps.executeQuery();

            if (rset.next()) {
                String value = getObjectValueById(rset.getInt(1));
                switch (annotationType.getAnnotationDefinitionType()) {
                case ENUMERATION:
                    for (String domainValue : StringUtils.split(value, "|")) {
                        domainValues.add(domainValue);
                    }
                    break;
                case REGULAR_EXPRESSION:
                    domainValues.add(value);
                    break;
                case URL:
                    //retrieve external resource
                    try {
                        AnnotationDefinition ad =
                                cacheableAnnotationDefinitionService
                                        .resolveAnnotationDefinition(value);
                        if (BELUtilities.hasItems(ad.getEnums())) {
                            //external enumerations
                            domainValues.addAll(ad.getEnums());
                        } else {
                            //pattern
                            domainValues.add(ad.getValue());
                        }
                    } catch (AnnotationDefinitionResolutionException e) {
                        throw new ExternalResourceException(e.getName(),
                                e.getMessage(), e);
                    }
                    break;
                }
            }
        } finally {
            close(rset);
        }

        // Add this list to the cache
        annotationTypeValueCache.put(annotationType.getId(), domainValues);

        return domainValues;
    }

    @Override
    public List<AnnotationType> getAnnotationTypes() throws SQLException {
        List<AnnotationType> list = new ArrayList<AnnotationType>();

        ResultSet rset = null;

        try {
            PreparedStatement ps =
                    getPreparedStatement(SELECT_ANNOTATION_TYPES_SQL);
            rset = ps.executeQuery();

            while (rset.next()) {
                AnnotationType annotationType = getAnnotationType(rset);
                addAnnotationType(list, annotationType);

                // This might save us time later on
                if (!annotationTypeCache.containsKey(annotationType.getId())) {
                    annotationTypeCache.put(annotationType.getId(),
                            annotationType);
                }
            }
        } finally {
            close(rset);
        }

        return list;
    }

    private AnnotationType createCitationType(AnnotationType annotationType) {
        return new AnnotationType(
                annotationType.getId(),
                CITATION,
                "The citation that supports one or more statements.",
                "Use this annotation to link statements to a citation.",
                AnnotationDefinitionType.REGULAR_EXPRESSION);
    }

    /**
     *
     * @param rset
     * @return
     * @throws SQLException
     */
    private AnnotationType getAnnotationType(ResultSet rset)
            throws SQLException {
        Integer annotationTypeId = rset.getInt(1);

        if (annotationTypeCache.containsKey(annotationTypeId)) {
            return annotationTypeCache.get(annotationTypeId);
        }

        String name = rset.getString(2);
        String description = rset.getString(3);
        String usage = rset.getString(4);
        Integer annotationDefinitionTypeId = rset.getInt(5);
        AnnotationDefinitionType annotationDefinitionType =
                AnnotationDefinitionType.fromValue(annotationDefinitionTypeId);

        final AnnotationType annotationType;
        if (annotationDefinitionType.equals(AnnotationDefinitionType.URL)) {
            ResultSet dvrset = null;
            try {
                PreparedStatement dvps =
                        getPreparedStatement(SELECT_ANNOTATION_TYPE_DOMAIN_VALUE_SQL);
                dvps.setInt(1, annotationTypeId);
                dvrset = dvps.executeQuery();

                String url = null;
                if (dvrset.next()) {
                    url = getObjectValueById(dvrset.getInt(1));
                }

                annotationType =
                        new AnnotationType(annotationTypeId, name, description,
                                usage, annotationDefinitionType, url);
            } finally {
                close(dvrset);
            }
        } else {
            annotationType =
                    new AnnotationType(annotationTypeId, name, description,
                            usage, annotationDefinitionType);
        }

        return annotationType;
    }

    /**
     *
     * @param nodes
     * @param params A {@link ResultSet} from a SELECT on the {@code kam_node_parameter}
     * table.
     * @return
     * @throws SQLException
     */
    private KamProtoNode getKamProtoNode(ResultSet nodes, ResultSet params)
            throws SQLException {
        Integer kamNodeId = nodes.getInt(1);
        Integer functionTypeId = nodes.getInt(2);
        String label = getObjectValueById(nodes.getInt(3));

        List<Integer> nodeParameterIds = new ArrayList<Integer>();

        while (params.next()) {
            if (params.getInt(1) != kamNodeId.intValue()) {
                params.previous(); // cursor poised for proper next() call
                break;
            }
            nodeParameterIds.add(params.getInt(2));
        }

        for (Integer nodeParameterId : nodeParameterIds) {
            label =
                    label.replaceFirst(ANY_NUMBER_PLACEHOLDER,
                            nodeParameterId.toString());
        }

        return new KamProtoNode(kamNodeId,
                FunctionEnum.fromValue(functionTypeId), label);
    }

    /**
     *
     * @param rset
     * @return
     * @throws SQLException
     */
    private Annotation getAnnotation(ResultSet rset) throws SQLException {
        Integer annotationId = rset.getInt(1);
        String label = getObjectValueById(rset.getInt(2));
        Integer annotationTypeId = rset.getInt(3);

        return new Annotation(annotationId,
                getAnnotationTypeById(annotationTypeId), label);
    }

    private Annotation getAnnotation(final Integer annotationId)
            throws SQLException {
        Annotation annotation = annotationCache.get(annotationId);
        if (annotation != null) {
            return annotation;
        }

        ResultSet rset = null;
        try {
            PreparedStatement ps =
                    getPreparedStatement(SELECT_ANNOTATION_BY_ID_SQL);
            ps.setInt(1, annotationId);
            rset = ps.executeQuery();

            if (rset.next()) {
                String label = getObjectValueById(rset.getInt(1));
                Integer annotationTypeId = rset.getInt(2);
                AnnotationType annotationType =
                        getAnnotationTypeById(annotationTypeId);

                annotation =
                        new Annotation(annotationId, annotationType, label);
                annotationCache.put(annotationId, annotation);

                return annotation;
            }
        } finally {
            close(rset);
        }

        return null;
    }

    /**
     * @param rset
     * @return
     * @throws SQLException
     */
    private Namespace getNamespace(ResultSet rset) throws SQLException {
        Integer namespaceId = rset.getInt(1);
        String prefix = rset.getString(2);
        String resourceLocation = getObjectValueById(rset.getInt(3));

        return new Namespace(namespaceId, prefix, resourceLocation);
    }

    /**
     *
     * @param rset
     * @return
     * @throws SQLException
     */
    private BelStatement getStatement(ResultSet rset) throws SQLException {
        Integer statementId = rset.getInt(1);
        Integer documentId = rset.getInt(2);
        Integer subjectTermId = rset.getInt(3);
        Integer relationshipTypeId = rset.getInt(4);
        boolean definitional = rset.wasNull();
        Integer objectTermId = rset.getInt(5);
        Integer nestedSubjectId = rset.getInt(6);
        Integer nestedRelationship = rset.getInt(7);
        Integer nestedObjectId = rset.getInt(8);

        // Get the document info for this statement
        BelDocumentInfo belDocumentInfo = getBelDocumentInfoById(documentId);
        // Lastly, get the annotations
        List<Annotation> annotations = getAnnotationsByStatementId(statementId);

        BelTerm subject = getBelTermById(subjectTermId);
        BelElement object = null;
        if (definitional) {
            return new BelStatement(statementId, subject, belDocumentInfo,
                    annotations);
        } else if (null != objectTermId && !objectTermId.equals(0)) {
            object = getBelTermById(objectTermId);
            return new BelStatement(statementId, subject,
                    fromValue(relationshipTypeId), object,
                    belDocumentInfo, annotations);
        } else {
            BelTerm nestedSubject = getBelTermById(nestedSubjectId);
            BelTerm nestedObject = getBelTermById(nestedObjectId);
            BelStatement nested =
                    new BelStatement(statementId, nestedSubject,
                            fromValue(nestedRelationship),
                            nestedObject,
                            belDocumentInfo, annotations);
            return new BelStatement(statementId, subject,
                    fromValue(relationshipTypeId), nested,
                    belDocumentInfo, annotations);
        }
    }

    /**
     *
     * @param rset
     * @return
     * @throws SQLException
     */
    private BelDocumentInfo getBelDocumentInfo(ResultSet rset)
            throws SQLException {
        Integer belDocumentId = rset.getInt(1);
        String name = rset.getString(2);
        String description = rset.getString(3);
        String version = rset.getString(4);
        String copyright = rset.getString(5);
        String disclaimer = rset.getString(6);
        String contactInfo = rset.getString(7);
        String licenseInfo = rset.getString(8);
        String authors = rset.getString(9);

        List<AnnotationType> annotationTypes =
                getAnnotationTypesByDocumentId(belDocumentId);
        List<Namespace> namespaces = getNamespacesByDocumentId(belDocumentId);

        return new BelDocumentInfo(belDocumentId, name, description, version,
                copyright, disclaimer, contactInfo, licenseInfo, authors,
                annotationTypes, namespaces);
    }

    /**
     * @param objectsTextId
     * @return
     * @throws SQLException
     */
    private String getObjectsTextById(Integer objectsTextId)
            throws SQLException {
        PreparedStatement ps = null;
        ResultSet rset = null;
        String value = null;
        try {
            ps = getPreparedStatement(SELECT_OBJECTS_TEXT_SQL);
            ps.setInt(1, objectsTextId);
            rset = ps.executeQuery();
            if (rset.next()) {
                value = rset.getString(1);
            }
        } finally {
            close(rset);
        }
        return value;
    }

    /**
     * {@inheritDoc}
     */
    private String getObjectValueById(Integer objectId) throws SQLException {

        // See if the object is cached
        if (objectValueCache.containsKey(objectId)) {
            return objectValueCache.get(objectId);
        }

        String value = null;
        //Integer typeId = null;
        PreparedStatement ps = null;
        ResultSet rset = null;
        int objectsTextId = 0;

        try {
            ps = getPreparedStatement(SELECT_OBJECTS_VALUE_SQL);
            ps.setInt(1, objectId);
            rset = ps.executeQuery();
            if (rset.next()) {
                //typeId = rset.getInt(1);
                value = rset.getString(2);
                objectsTextId = rset.getInt(3);
            }
            if (value == null && objectsTextId != 0) { //value is in objects_text table
                value = getObjectsTextById(objectsTextId);
            }

            if (value == null) {
                value = "";
            }
        } finally {
            close(rset);
        }

        // Push into the cache
        objectValueCache.put(objectId, value);
        objectValueReverseCache.put(value, objectId);

        return value;
    }

    /**
     * TODO: This will fail if the object value is stored in the text table
     *
     * @param value
     * @return
     * @throws SQLException
     */
    private Integer getObjectIdByValue(final String value)
            throws SQLException {
        if (value == null) {
            return null;
        }
        // Check to see if the objectValue has already been seen
        if (objectValueReverseCache.containsKey(value)) {
            return objectValueReverseCache.get(value);
        }

        ResultSet rset = null;
        Integer objectId = null;

        try {
            PreparedStatement ps = getPreparedStatement(SELECT_OBJECTS_ID_SQL);
            ps.setString(1, value);

            rset = ps.executeQuery();
            if (rset.next()) {
                objectId = rset.getInt(1);
            }
        } finally {
            close(rset);
        }

        // Push into the cache
        if (objectId != null) {
            objectValueCache.put(objectId, value);
            objectValueReverseCache.put(value, objectId);
        }

        return objectId;
    }

    /**
     * Retrieves a {@link Set set} of all {@link String citation definitions}.
     *
     * @return the {@link Set set}
     */
    private static Set<String> getCitationDefinitions() {

        final Set<String> citationKeys =
                sizedHashSet(KAMStoreConstants.CITATION_ANNOTATION_DEFINITION_IDS.length);
        citationKeys.addAll(Arrays
                .asList(KAMStoreConstants.CITATION_ANNOTATION_DEFINITION_IDS));

        return citationKeys;
    }

    /**
     *
     * @author julianjray
     */
    public final static class TermParameter extends KamElementImpl {
        private final Namespace namespace;
        private final String parameterValue;

        protected TermParameter(Integer id, Namespace namespace,
                String parameterValue) {
            super(null, id);
            this.namespace = namespace;
            this.parameterValue = parameterValue;
        }

        public Namespace getNamespace() {
            return namespace;
        }

        public String getParameterValue() {
            return parameterValue;
        }
    }

    /**
     * {@link KamProtoNode} represents a {@link Kam kam} node retrieved from
     * a KAM schema.
     *
     * @author julianjray
     */
    public final static class KamProtoNode extends KamElementImpl {
        private final FunctionEnum functionType;
        private final String label;
        private int hash;

        /**
         * Constructs the {@link KamProtoNode kam proto node}.
         *
         * <p>
         * The hashcode is precomputed since {@link KamProtoNode} is immutable.
         * </p>
         *
         * @param id the database {@link Integer id} of the {@link Kam kam}
         * node.
         * @param functionType the {@link Kam kam} node
         * {@link FunctionEnum function}
         * @param label the node {@link String label}
         * @throws InvalidArgument Thrown if {@code functionType} or
         * {@code label} is {@code null}
         */
        private KamProtoNode(final Integer id, final FunctionEnum functionType,
                final String label) {
            super(null, id);

            if (functionType == null) {
                throw new InvalidArgument("functionType", functionType);
            }

            if (label == null) {
                throw new InvalidArgument("label", label);
            }

            this.functionType = functionType;
            this.label = label;
            hash = computeHash();
        }

        /**
         * Retrieves the {@link FunctionEnum function type}.
         *
         * @return the {@link FunctionEnum function type}, which will be
         * {@code non-null}
         */
        public FunctionEnum getFunctionType() {
            return functionType;
        }

        /**
         * Retrieves the {@link String label}.
         *
         * @return the {@link String label}, which will be {@code non-null}
         */
        public String getLabel() {
            return label;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals(Object o) {
            if (o == null || !(o instanceof KamProtoNode)) {
                return false;
            }
            KamProtoNode other = (KamProtoNode) o;

            if (!(this.functionType.equals(other.functionType))) {
                return false;
            }

            // The label may contain '#' instead of a string of numbers, so this method
            // needs to check number-patterns separately.  '#' matches '#' or any
            // number, but otherwise the numbers and non-number substrings are matched
            // exactly.

            final Matcher thisMatcher =
                    ANY_NUMBER_REGEX_PATTERN.matcher(this.label), otherMatcher =
                    ANY_NUMBER_REGEX_PATTERN.matcher(other.label);

            int i = 0, j = 0;
            final int iMax = this.label.length(), jMax = other.label.length();
            while (true) {
                final boolean b1 = thisMatcher.find(i);
                final boolean b2 = otherMatcher.find(j);
                if (b1 != b2) {
                    // One label has more instances of the pattern than the other,
                    // so they can not be equal.
                    return false;

                } else if (!b1) {
                    // There are no more number patterns in either this.label or
                    // other.label, so simply compare the rest of the labels.
                    return substringEquals(this.label, i, iMax, other.label, j,
                            jMax);

                } else if (!substringEquals(this.label, i, thisMatcher.start(),
                        other.label, j, otherMatcher.start())) {
                    // The substring before the next pattern match are not equal,
                    // so the labels can not be equal.
                    return false;

                } else if (substringEquals(this.label, thisMatcher.start(),
                        thisMatcher.end(),
                        ANY_NUMBER_PLACEHOLDER, 0,
                        ANY_NUMBER_PLACEHOLDER_LENGTH)
                        ||
                        substringEquals(other.label, otherMatcher.start(),
                                otherMatcher.end(),
                                ANY_NUMBER_PLACEHOLDER, 0,
                                ANY_NUMBER_PLACEHOLDER_LENGTH)
                        ||
                        substringEquals(this.label, thisMatcher.start(),
                                thisMatcher.end(),
                                other.label, otherMatcher.start(),
                                otherMatcher.end())) {

                    // this.label and other.label have matching patterns, so continue the search
                    // after incrementing i and j.
                    i = thisMatcher.end();
                    j = otherMatcher.end();

                } else {
                    // The numbers do not match, so the labels are not equal.
                    return false;
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode() {
            return hash;
        }

        private int computeHash() {
            // The label may contain '#' instead of a number.  Compute the hash
            // as if all numbers in the label have been replaced with '#'.
            final int labelHashCode = NUMBER_REGEX_PATTERN.matcher(this.label)
                    .replaceAll(ANY_NUMBER_PLACEHOLDER).hashCode();

            // TODO improve implementation
            return this.functionType.hashCode() ^ labelHashCode;
        }
    }

    /**
     * {@link KamProtoEdge} represents a {@link Kam kam} edge retrieved from
     * a KAM schema.
     *
     * @author julianjray
     */
    public final static class KamProtoEdge extends KamElementImpl {
        private final KamProtoNode sourceNode;
        private final KamProtoNode targetNode;
        private final RelationshipType relationshipType;
        private int hash;

        /**
         * Constructs the {@link KamProtoEdge kam proto edge}.
         *
         * <p>
         * The hashcode is precomputed since {@link KamProtoEdge} is immutable.
         * </p>
         *
         * @param kamEdgeId the database {@link Integer id} of the
         * {@link Kam kam} node.
         * @param sourceNode the source {@link KamProtoNode node} for this edge
         * @param relationshipType the {@link RelationshipType relationship}
         * for this edge
         * @param targetNode the target {@link KamProtoNode node} for this edge
         * @throws InvalidArgument Thrown if {@code sourceNode},
         * {@code RelationshipType}, or {@code targetNode} is {@code null}
         */
        private KamProtoEdge(Integer kamEdgeId, KamProtoNode sourceNode,
                RelationshipType relationshipType, KamProtoNode targetNode) {
            super(null, kamEdgeId);

            if (sourceNode == null) {
                throw new InvalidArgument("sourceNode", sourceNode);
            }

            if (relationshipType == null) {
                throw new InvalidArgument("relationshipType", relationshipType);
            }

            if (targetNode == null) {
                throw new InvalidArgument("targetNode", targetNode);
            }

            this.sourceNode = sourceNode;
            this.relationshipType = relationshipType;
            this.targetNode = targetNode;
            this.hash = computeHash();
        }

        /**
         * Retrieves the source {@link KamProtoNode node}.
         *
         * @return the source {@link KamProtoNode node}, which will be
         * {@code non-null}
         */
        public KamProtoNode getSourceNode() {
            return sourceNode;
        }

        /**
         * Retrieves the target {@link KamProtoNode node}.
         *
         * @return the target {@link KamProtoNode node}, which will be
         * {@code non-null}
         */
        public KamProtoNode getTargetNode() {
            return targetNode;
        }

        /**
         * Retrieves the {@link RelationshipType relationship}.
         *
         * @return the edge's {@link RelationshipType relationship}, which will
         * be {@code non-null}
         */
        public RelationshipType getRelationship() {
            return relationshipType;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals(Object o) {
            if ((o == null) || !(o instanceof KamProtoEdge)) {
                return false;
            }
            KamProtoEdge other = (KamProtoEdge) o;
            return (this.sourceNode.equals(other.sourceNode) &&
                    this.targetNode.equals(other.targetNode) && this.relationshipType
                        .equals(other.relationshipType));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode() {
            return hash;
        }

        private int computeHash() {
            // TODO improve implementation
            return this.sourceNode.hashCode() ^ this.targetNode.hashCode()
                    ^ this.relationshipType.hashCode();
        }
    }

    /**
     *
     * @author julianjray
     */
    public final static class Namespace extends KamStoreObjectImpl {
        private final String prefix;
        private final String resourceLocation;

        protected Namespace(Integer namespaceId, String prefix,
                String resourceLocation) {
            super(namespaceId);
            this.prefix = prefix;
            this.resourceLocation = resourceLocation;
        }

        public String getPrefix() {
            return prefix;
        }

        public String getResourceLocation() {
            return resourceLocation;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            } else if (obj == null) {
                return false;
            } else if (!(obj instanceof Namespace)) {
                return false;
            } else {
                Namespace other = (Namespace) obj;
                return (BELUtilities.equals(getId(), other.getId()) &&
                        BELUtilities.equals(prefix, other.prefix) && BELUtilities
                            .equals(resourceLocation, other.resourceLocation));
            }
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int hash = 0;

            final Integer id = getId();
            hash += (id != null ? id.hashCode() : 0);
            hash *= prime;
            hash += (prefix != null ? prefix.hashCode() : 0);
            hash *= prime;
            hash +=
                    (resourceLocation != null ? resourceLocation.hashCode() : 0);
            return hash;
        }
    }

    /**
     *
     * @author julianjray
     */
    public static final class BelDocumentInfo extends KamStoreObjectImpl {
        private final String name;
        private final String description;
        private final String version;
        private final String copyright;
        private final String disclaimer;
        private final String contactInfo;
        private final String licenseInfo;
        private final String authors;
        private final List<AnnotationType> annotationTypes;
        private final List<Namespace> namespaces;

        public BelDocumentInfo(Integer belDocumentId, String name,
                String description, String version, String copyright,
                String disclaimer, String contactInfo, String licenseInfo,
                String authors, List<AnnotationType> annotationTypes,
                List<Namespace> namespaces) {
            super(belDocumentId);
            this.name = name;
            this.description = description;
            this.version = version;
            this.copyright = copyright;
            this.disclaimer = disclaimer;
            this.contactInfo = contactInfo;
            this.licenseInfo = licenseInfo;
            this.authors = authors;

            this.annotationTypes = new ArrayList<AnnotationType>();
            this.annotationTypes.addAll(annotationTypes);

            this.namespaces = new ArrayList<Namespace>();
            this.namespaces.addAll(namespaces);
        }

        public List<AnnotationType> getAnnotationTypes() {
            List<AnnotationType> list = new ArrayList<AnnotationType>();
            list.addAll(this.annotationTypes);
            return list;
        }

        public List<Namespace> getNamespaces() {
            List<Namespace> list = new ArrayList<Namespace>();
            list.addAll(this.namespaces);
            return list;
        }

        public String getName() {
            return name;
        }

        public String getDescription() {
            return description;
        }

        public String getVersion() {
            return version;
        }

        public String getCopyright() {
            return copyright;
        }

        public String getDisclaimer() {
            return disclaimer;
        }

        public String getContactInfo() {
            return contactInfo;
        }

        public String getLicenseInfo() {
            return licenseInfo;
        }

        public String getAuthors() {
            return authors;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            } else if (obj == null) {
                return false;
            } else if (!(obj instanceof BelDocumentInfo)) {
                return false;
            } else {
                BelDocumentInfo other = (BelDocumentInfo) obj;
                return (BELUtilities.equals(getId(), other.getId())
                        &&
                        BELUtilities.equals(name, other.name)
                        &&
                        BELUtilities.equals(description, other.description)
                        &&
                        BELUtilities.equals(version, other.version)
                        &&
                        BELUtilities.equals(copyright, other.copyright)
                        &&
                        BELUtilities.equals(disclaimer, other.disclaimer)
                        &&
                        BELUtilities.equals(contactInfo, other.contactInfo)
                        &&
                        BELUtilities.equals(licenseInfo, other.licenseInfo)
                        &&
                        BELUtilities.equals(authors, other.authors)
                        &&
                        BELUtilities.equals(annotationTypes,
                                other.annotationTypes) && BELUtilities.equals(
                        namespaces, other.namespaces));
            }
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int hash = 0, annotationTypesHash = 0, namespacesHash = 0;
            if (annotationTypes != null) {
                for (AnnotationType annotationType : annotationTypes) {
                    annotationTypesHash ^= annotationType.hashCode();
                }
            }
            if (namespaces != null) {
                for (Namespace namespace : namespaces) {
                    namespacesHash ^= namespace.hashCode();
                }
            }

            final Integer id = getId();
            hash += (id != null ? id.hashCode() : 0);
            hash *= prime;
            hash += (name != null ? name.hashCode() : 0);
            hash *= prime;
            hash += (description != null ? description.hashCode() : 0);
            hash *= prime;
            hash += (version != null ? version.hashCode() : 0);
            hash *= prime;
            hash += (copyright != null ? copyright.hashCode() : 0);
            hash *= prime;
            hash += (disclaimer != null ? disclaimer.hashCode() : 0);
            hash *= prime;
            hash += (contactInfo != null ? contactInfo.hashCode() : 0);
            hash *= prime;
            hash += (licenseInfo != null ? licenseInfo.hashCode() : 0);
            hash *= prime;
            hash += (authors != null ? authors.hashCode() : 0);
            hash *= prime;
            hash += annotationTypesHash;
            hash *= prime;
            hash += namespacesHash;
            return hash;
        }
    }

    /**
     *
     * @author julianjray
     */
    public static final class Citation {
        private final String name;
        private final String id;
        private final String comment;
        private final Date publicationDate;
        private final List<String> authors;
        private final CitationType citationType;

        public Citation(String name, String id, String comment,
                Date publicationDate, List<String> authors,
                CitationType citationType) {
            this.name = name;
            this.id = id;
            this.comment = comment;
            this.publicationDate = publicationDate;
            this.authors = authors;
            this.citationType = citationType;
        }

        private Citation(List<Annotation> annotationList) {
            String name = null;
            String id = null;
            String comment = null;
            Date publicationDate = null;
            List<String> authors = null;
            CitationType citationType = null;

            for (Annotation annotation : annotationList) {
                if (CitationReferenceAnnotationDefinition.ANNOTATION_DEFINITION_ID
                        .equals(annotation.getAnnotationType().getName())) {
                    //citation reference id
                    id = annotation.getValue();
                } else if (CitationDateAnnotationDefinition.ANNOTATION_DEFINITION_ID
                        .equals(annotation.getAnnotationType().getName())) {
                    if (annotation.getValue() != null) {
                        try {
                            publicationDate =
                                    dateFormat.parse(annotation.getValue());
                        } catch (ParseException e) {
                            publicationDate = null;
                        }
                    }
                } else if (CitationNameAnnotationDefinition.ANNOTATION_DEFINITION_ID
                        .equals(annotation.getAnnotationType().getName())) {
                    name = annotation.getValue();
                } else if (CitationCommentAnnotationDefinition.ANNOTATION_DEFINITION_ID
                        .equals(annotation.getAnnotationType().getName())) {
                    comment = annotation.getValue();
                } else if (CitationTypeAnnotationDefinition.ANNOTATION_DEFINITION_ID
                        .equals(annotation.getAnnotationType().getName())) {
                    citationType =
                            CitationType.getCitationType(annotation.getValue());
                } else if (CitationAuthorsAnnotationDefinition.ANNOTATION_DEFINITION_ID
                        .equals(annotation.getAnnotationType().getName())) {
                    if (annotation.getValue() != null) {
                        authors = PackUtils.unpackValues(annotation.getValue());
                    }
                }
            }
            this.name = name;
            this.id = id;
            this.comment = comment;
            this.publicationDate = publicationDate;
            this.authors = authors;
            this.citationType = citationType;
        }

        public String getName() {
            return name;
        }

        public String getId() {
            return id;
        }

        public String getComment() {
            return comment;
        }

        public Date getPublicationDate() {
            return publicationDate;
        }

        public List<String> getAuthors() {
            return authors;
        }

        public CitationType getCitationType() {
            return citationType;
        }

        @Override
        public boolean equals(Object obj) {

            if (obj == null) {
                return false;
            } else if (!(obj instanceof Citation)) {
                return false;
            } else {
                Citation other = (Citation) obj;
                return (BELUtilities.equals(name, other.name)
                        &&
                        BELUtilities.equals(id, other.id)
                        &&
                        BELUtilities.equals(comment, other.comment)
                        &&
                        BELUtilities.equals(publicationDate,
                                other.publicationDate) &&
                        BELUtilities.equals(authors, other.authors) && BELUtilities
                            .equals(citationType, other.citationType));
            }
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int hash = 0;
            int authorsHash = 0;

            if (authors != null) {
                for (String author : authors) {
                    authorsHash ^= author.hashCode();
                }
            }

            hash *= prime;
            hash += (name != null ? name.hashCode() : 0);
            hash *= prime;
            hash += (id != null ? id.hashCode() : 0);
            hash *= prime;
            hash += (comment != null ? comment.hashCode() : 0);
            hash *= prime;
            hash += (publicationDate != null ? publicationDate.hashCode() : 0);
            hash *= prime;
            hash += authorsHash;
            hash *= prime;
            hash += (citationType != null ? citationType.hashCode() : 0);
            return hash;
        }
    }

    /**
     *
     * @author julianjray
     */
    public final static class BelStatement extends BelElement {
        private final BelDocumentInfo belDocumentInfo;
        private final BelTerm subject;
        private final RelationshipType relationshipType;
        private final BelElement object;
        private final List<Annotation> annotationList;
        private final Citation citation;

        private BelStatement(Integer belStatementId, BelTerm subject,
                BelDocumentInfo belDocumentInfo, List<Annotation> annotationList) {
            super(belStatementId);
            this.subject = subject;
            this.relationshipType = null;
            this.object = null;
            this.belDocumentInfo = belDocumentInfo;
            this.annotationList = new ArrayList<Annotation>();
            for (Annotation annotation : annotationList) {
                if (!ArrayUtils.contains(
                        KAMStoreConstants.CITATION_ANNOTATION_DEFINITION_IDS,
                        annotation.getAnnotationType().getName())) {
                    //non citation annotations
                    this.annotationList.add(annotation);
                }
            }
            this.citation = new Citation(annotationList);
        }

        private BelStatement(Integer belStatementId, BelTerm subject,
                RelationshipType relationshipType, BelElement object,
                BelDocumentInfo belDocumentInfo, List<Annotation> annotationList) {
            super(belStatementId);
            this.subject = subject;
            this.relationshipType = relationshipType;
            this.object = object;
            this.belDocumentInfo = belDocumentInfo;
            this.annotationList = new ArrayList<Annotation>();
            for (Annotation annotation : annotationList) {
                if (!ArrayUtils.contains(
                        KAMStoreConstants.CITATION_ANNOTATION_DEFINITION_IDS,
                        annotation.getAnnotationType().getName())) {
                    //non citation annotations
                    this.annotationList.add(annotation);
                }
            }
            this.citation = new Citation(annotationList);
        }

        /**
         * returns a list which immutable with respect to the statement
         * @return
         */
        public List<Annotation> getAnnotationList() {
            List<Annotation> list = new ArrayList<Annotation>();
            list.addAll(this.annotationList);
            return list;
        }

        /**
         * returns a citation object associated with this statement
         * @return
         */
        public Citation getCitation() {
            return citation;
        }

        public BelDocumentInfo getBelDocumentInfo() {
            return belDocumentInfo;
        }

        public BelTerm getSubject() {
            return subject;
        }

        public RelationshipType getRelationshipType() {
            return relationshipType;
        }

        public BelElement getObject() {
            return object;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append(subject.toString());
            if (relationshipType == null) {
                // definitional statement
                return sb.toString();
            }

            sb.append(" ");
            sb.append(relationshipType.getDisplayValue());

            if (object instanceof BelStatement) {
                // nested statement
                sb.append(" (");
                sb.append(object.toString());
                sb.append(" )");
                return sb.toString();
            }

            // simple statement
            sb.append(" ");
            sb.append(object.toString());
            return sb.toString();
        }
    }

    /**
     *
     * @author julianjray
     */
    public final static class BelTerm extends BelElement {
        private final String label;

        protected BelTerm(Integer belTermId, String label) {
            super(belTermId);
            this.label = label;
        }

        public String getLabel() {
            return label;
        }

        @Override
        public String toString() {
            return label;
        }
    }

    /**
     *
     * @author julianjray
     */
    public final static class Annotation extends KamStoreObjectImpl {

        private final AnnotationType annotationType;
        private final String value;

        private Annotation(Integer annotationId, AnnotationType annotationType,
                String value) {
            super(annotationId);
            this.value = value;
            this.annotationType = annotationType;
        }

        public AnnotationType getAnnotationType() {
            return annotationType;
        }

        public String getValue() {
            return value;
        }
    }

    /**
     *
     * @author julianjray
     *
     */
    public static final class AnnotationType extends KamStoreObjectImpl {

        private final String name;
        private final String description;
        private final String usage;
        private final AnnotationDefinitionType annotationDefinitionType;
        private final String url;

        public AnnotationType(Integer annotationTypeId, String name,
                String description, String usage,
                AnnotationDefinitionType annotationDefinitionType) {
            super(annotationTypeId);
            this.name = name;
            this.description = description;
            this.usage = usage;
            this.annotationDefinitionType = annotationDefinitionType;
            this.url = null;
        }

        private AnnotationType(Integer annotationTypeId, String name,
                String description, String usage,
                AnnotationDefinitionType annotationDefinitionType, String url) {
            super(annotationTypeId);
            this.name = name;
            this.description = description;
            this.usage = usage;
            this.annotationDefinitionType = annotationDefinitionType;
            this.url = url;
        }

        public String getName() {
            return name;
        }

        public String getDescription() {
            return description;
        }

        public String getUsage() {
            return usage;
        }

        public AnnotationDefinitionType getAnnotationDefinitionType() {
            return annotationDefinitionType;
        }

        /**
         * Returns the url of this annotation type.
         *
         * @return {@link String} the url where the annotation is defined,
         * which may be <tt>null</tt>
         */
        public String getUrl() {
            return url;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            } else if (obj == null) {
                return false;
            } else if (!(obj instanceof AnnotationType)) {
                return false;
            } else {
                AnnotationType other = (AnnotationType) obj;
                return (BELUtilities.equals(getId(), other.getId())
                        &&
                        BELUtilities.equals(name, other.name)
                        &&
                        BELUtilities.equals(description, other.description)
                        &&
                        BELUtilities.equals(usage, other.usage)
                        &&
                        BELUtilities.equals(annotationDefinitionType,
                                other.annotationDefinitionType) && BELUtilities
                            .equals(url, other.url));
            }
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int hash = 0;
            final Integer id = getId();
            hash += (id != null ? id.hashCode() : 0);
            hash *= prime;
            hash += (name != null ? name.hashCode() : 0);
            hash *= prime;
            hash += (description != null ? description.hashCode() : 0);
            hash *= prime;
            hash += (usage != null ? usage.hashCode() : 0);
            hash *= prime;
            hash +=
                    (annotationDefinitionType != null ? annotationDefinitionType
                            .hashCode()
                            : 0);
            hash *= prime;
            hash += (url != null ? url.hashCode() : 0);
            return hash;
        }
    }

    /**
     * Annotation definition type
     *
     */
    public enum AnnotationDefinitionType {

        /**
         * An annotation whose value must match one from an enumerated list.
         */
        ENUMERATION(0, "listAnnotation"),

        /**
         * An annotation whose value must match a regular expression.
         */
        REGULAR_EXPRESSION(1, "patternAnnotation"),

        /**
         * An annotation whose value is specified by a URL
         */
        URL(2, "urlAnnotation");

        /**
         * Value unique to each enumeration.
         */
        private final Integer value;
        /**
         * Enumeration display value.
         */
        private final String displayValue;

        /**
         * Constructor for setting enum and display value.
         *
         * @param value Enum value
         * @param displayValue Display value
         */
        private AnnotationDefinitionType(Integer value, String displayValue) {
            this.value = value;
            this.displayValue = displayValue;
        }

        /**
         * Returns the annotation type by its integer value.
         * @param value
         * @return
         */
        public static AnnotationDefinitionType fromValue(final Integer value) {
            AnnotationDefinitionType type = null;
            if (value != null) {
                for (AnnotationDefinitionType adt : AnnotationDefinitionType
                        .values()) {
                    if (value.equals(adt.value)) {
                        type = adt;
                        break;
                    }
                }
            }
            return type;
        }

        public Integer getValue() {
            return value;
        }

        public String getDisplayValue() {
            return displayValue;
        }
    }

    private class _KamEdge implements SimpleKAMEdge {
        private final int id;
        private final int source;
        private final int target;
        private final RelationshipType relationship;

        _KamEdge(int id, RelationshipType r, int srcID, int tgtID) {
            this.id = id;
            this.relationship = r;
            this.source = srcID;
            this.target = tgtID;
        }

        @Override
        public int getID() {
            return id;
        }

        @Override
        public int getSourceID() {
            return source;
        }

        @Override
        public int getTargetID() {
            return target;
        }

        @Override
        public RelationshipType getRelationship() {
            return relationship;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result *= prime;
            result += id;
            result *= prime;
            result += relationship.hashCode();
            result *= prime;
            result += source;
            result *= prime;
            result += target;
            return result;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null) return false;
            if (!(obj instanceof _KamEdge)) return false;
            _KamEdge other = (_KamEdge) obj;
            if (id != other.id) return false;
            if (relationship != other.relationship) return false;
            if (source != other.source) return false;
            if (target != other.target) return false;
            return true;
        }

    }

    private class _KamNode implements SimpleKAMNode {
        private final int id;
        private final FunctionEnum function;
        private final String label;

        _KamNode(int id, FunctionEnum fx, String label) {
            this.id = id;
            this.function = fx;
            this.label = label;
        }

        @Override
        public int getID() {
            return id;
        }

        @Override
        public FunctionEnum getFunction() {
            return function;
        }

        @Override
        public String getLabel() {
            return label;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result *= prime;
            result += function.hashCode();
            result *= prime;
            result += id;
            result *= prime;
            result += label.hashCode();
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null) return false;
            if (!(obj instanceof _KamNode)) return false;
            _KamNode other = (_KamNode) obj;
            if (function != other.function) return false;
            if (id != other.id) return false;
            if (!label.equals(other.label)) return false;
            return true;
        }

    }
}
TOP

Related Classes of org.openbel.framework.api.internal.KAMStoreDaoImpl$BelTerm

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
y>