gt:             map.put(new MultiKey("prop", "MRNA", "cds"),
context: package org.intermine.bio.dataconversion;
  
  /*
   * Copyright (C) 2002-2017 FlyMine
   *
   * This code may be freely distributed and modified under the
   * terms of the GNU Lesser General Public Licence.  This should
   * be distributed with the code.  See the LICENSE file for more
   * information or http://www.gnu.org/copyleft/lesser.html.
   *
   */
  
  import java.sql.Connection;
  import java.sql.ResultSet;
  import java.sql.SQLException;
  import java.sql.Statement;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  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 org.apache.commons.collections.keyvalue.MultiKey;
  import org.apache.commons.collections.map.MultiKeyMap;
  import org.apache.commons.lang.StringUtils;
  import org.apache.log4j.Logger;
  import org.intermine.bio.chado.config.ConfigAction;
  import org.intermine.bio.chado.config.SetFieldConfigAction;
  import org.intermine.objectstore.ObjectStoreException;
  import org.intermine.metadata.TypeUtil;
  import org.intermine.xml.full.Attribute;
  import org.intermine.xml.full.Item;
  import org.intermine.xml.full.Reference;
  
  /**
   * A processor that loads feature referred to by the modENCODE metadata.  This class is designed
   * to be used by ModEncodeMetaDataProcessor and will be called once for each submission that has
   * metadata.
   * @author Kim Rutherford
   */
  public class ModEncodeFeatureProcessor extends SequenceProcessor
  {
      private static final Logger LOG = Logger.getLogger(ModEncodeFeatureProcessor.class);
      private final String dataSetIdentifier;
      private final String title;
      private final String scoreProtocolItemId;
      private final String dataIdsTableName;
      private Set<String> commonFeatureInterMineTypes = new HashSet<String>();
      private static final String SUBFEATUREID_TEMP_TABLE_NAME = "modmine_subfeatureid_temp";
      private static final String BINDING_SITE_FEATS =
              "'binding_site', 'protein_binding_site', 'TF_binding_site', 'histone_binding_site'";
      // feature type to query from the feature table
      private static final List<String> FEATURES = Arrays.asList(
              "gene", "mRNA", "transcript",
              "CDS", "intron", "exon", "EST",
              "five_prime_untranslated_region",
              "five_prime_UTR", "three_prime_untranslated_region",
              "three_prime_UTR", "origin_of_replication",
              "binding_site", "protein_binding_site", "TF_binding_site",
              "insulator_binding_site",
              "transcript_region", "histone_binding_site", "copy_number_variation",
              "natural_transposable_element", "start_codon", "stop_codon"
              , "cDNA", "cDNA_match", "miRNA"
              , "three_prime_RACE_clone", "three_prime_RST", "three_prime_UST"
              , "polyA_site", "polyA_signal_sequence", "overlapping_EST_set", "exon_region"
              , "SL1_acceptor_site", "SL2_acceptor_site"
              , "transcription_end_site", "TSS", "under-replicated-region"
              , "full_transcript", "polypeptide_region", "peptide_collection"
              , "chromatin_state"
      );
      // the FB name for the mitochondrial genome
      private static final String MITOCHONDRION = "dmel_mitochondrion_genome";
      // ...
      private static final String CHROMOSOME = "Chromosome";
      // the configuration for this processor, set when getConfig() is called the first time
      private final Map<Integer, MultiKeyMap> config = new HashMap<Integer, MultiKeyMap>();
      private Map<Integer, FeatureData> commonFeaturesMap = new HashMap<Integer, FeatureData>();
      // list of modelled attributes for expression levels
      private static final Set<String> EL_KNOWN_ATTRIBUTES =
              Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
                      "dcpm",
                      "dcpm_bases",
                      "prediction_status",
                      "read_count",
                      "transcribed")));
      private static final String SUB_3154_TITLE = "MXEMB_N2_RNA_EXPRESSION";
      //    private static final String MODENCODE_SOURCE_NAME = "modENCODE";
  
      /**
       * Create a new ModEncodeFeatureProcessor.
       * @param chadoDBConverter     the parent converter
       * @param dataSetIdentifier    the item identifier of the DataSet,
       *                             i.e. the submissionItemIdentifier
       * @param dataSourceIdentifier the item identifier of the DataSource,
       *                             i.e. the labItemIdentifier
       * @param dataIdsTableName     name of a temporary table containing the data ids for the sub
       * @param title                the title
       * @param scoreProtocolItemId  score protocol item id
       */
      public ModEncodeFeatureProcessor(ChadoDBConverter chadoDBConverter,
              String dataSetIdentifier, String dataSourceIdentifier,
              String dataIdsTableName, String title, String scoreProtocolItemId) {
          super(chadoDBConverter);
          this.dataSetIdentifier = dataSetIdentifier;
          this.title = title;
          this.scoreProtocolItemId = scoreProtocolItemId;
          this.dataIdsTableName = dataIdsTableName;
          for (String chromosomeType : getChromosomeFeatureTypes()) {
              commonFeatureInterMineTypes.add(
                      TypeUtil.javaiseClassName(fixFeatureType(chromosomeType)));
          }
          commonFeatureInterMineTypes.add("Gene");
          commonFeatureInterMineTypes.add("MRNA");
          commonFeatureInterMineTypes.add("Transcript");
          commonFeatureInterMineTypes.add("CDNA");
          commonFeatureInterMineTypes.add("EST");
          commonFeatureInterMineTypes.add("CDS");
      }
      /**
       * Get a list of the chado/so types of the LocatedSequenceFeatures we wish to load.  The list
       * will not include chromosome-like features.
       * @return the list of features
       */
      @Override
      protected List<String> getFeatures() {
          return FEATURES;
      }
      /**
       * Get a map of features that are expected to be common between submissions.  This map can be
       * used to initialise this processor for a subsequent run.  The feature types added to this map
       * are governed by the addToFeatureMap method in this class.
       * @return a map of chado feature id to FeatureData objects
       */
      protected Map<Integer, FeatureData> getCommonFeaturesMap() {
          return commonFeaturesMap;
      }
      /**
       * Initialise SequenceProcessor with features that have already been processed and put the same
       * features data into a commonFeaturesMap that tracks features (e.g. Chromosomes) that appear
       * in multiple submissions but should only be processed once.
       * @param initialMap map of chado feature id to FeatureData objects
       */
      protected void initialiseCommonFeatures(Map<Integer, FeatureData> initialMap) {
          super.initialiseFeatureMap(initialMap);
          commonFeaturesMap.putAll(initialMap);
      }
      /**
       * {@inheritDoc}
       */
      @Override
      protected String getExtraFeatureConstraint() {
          return "(cvterm.name = 'chromosome' OR cvterm.name = 'chromosome_arm') AND "
                  + " feature_id IN ( SELECT featureloc.srcfeature_id "
                  + " FROM featureloc, " + SUBFEATUREID_TEMP_TABLE_NAME
                  + " WHERE featureloc.feature_id = " + SUBFEATUREID_TEMP_TABLE_NAME + ".feature_id) "
                  + " OR feature_id IN ( SELECT feature_id "
                  + " FROM " + SUBFEATUREID_TEMP_TABLE_NAME + " ) ";
      }
      /**
       * {@inheritDoc}
       */
      @Override
      protected void extraProcessing(Connection connection, Map<Integer, FeatureData> featureDataMap)
          throws ObjectStoreException, SQLException {
          // TODO: check if there is already a method to get all the match types
          // (and merge the methods)
          // also: add match to query and do everything here
          // process indirect locations via match features and featureloc feature<->match<->feature
          ResultSet matchLocRes = getMatchLocResultSet(connection);
          processLocationTable(connection, matchLocRes);
          List<String> types = getMatchTypes(connection);
          Iterator<String> t = types.iterator();
          while (t.hasNext()) {
              String featType = t.next();
              if ("cDNA_match".equalsIgnoreCase(featType)) {
                  continue;
              }
              ResultSet matchTypeLocRes = getMatchLocResultSet(connection, featType);
              processLocationTable(connection, matchTypeLocRes);
          }
          // adding scores
          processFeatureScores(connection);
          // do experimental features (expression levels)
          processExpressionLevels(connection);
          // adding sources (result files) for binding sites
          processPeaksSources(connection);
      }
  
  
      /**
       * Make a Location between a SequenceFeature and a Chromosome.
       * @param start the start position
       * @param end the end position
       * @param strand the strand
       * @param srcFeatureData the FeatureData for the src feature (the Chromosome)
       * @param featureData the FeatureData for the SequenceFeature
       * @param taxonId the taxon id to use when finding the Chromosome for the Location
       * @param featureId the chado feature id, key to our commonFeaturesMap
       * @return the new Location object
       * @throws ObjectStoreException if there is a problem while storing
       */
      @Override
      protected Item makeLocation(int start, int end, int strand, FeatureData srcFeatureData,
              FeatureData featureData, int taxonId, int featureId)
          throws ObjectStoreException {
          // if a common feature, do it only once..
          if (commonFeaturesMap.containsKey(featureId)) {
              return null;
          }
          Item location = getChadoDBConverter().makeLocation(srcFeatureData.getItemIdentifier(),
                  featureData.getItemIdentifier(),
                  start, end, strand, taxonId);
          return location;
      }
  
  
      /**
       * Method to set the source for gene
       * for modencode datasources it will add the title
       * @param fdat the FeatureData object
       * @param dataSourceName the data source
       * @throws ObjectStoreException the exception
       *
       */
      @Override
      //    protected void setGeneSource(Integer imObjectId, String dataSourceName)
      //        throws ObjectStoreException {
      //        String source = dataSourceName + "-" + title;
      //        setAttribute(imObjectId, "source", source);
      //    }
      protected void setGeneSource(FeatureData fdat, String dataSourceName)
          throws ObjectStoreException {
          String source = dataSourceName + "-" + title;
          Integer imObjectId = fdat.getIntermineObjectId();
          setAttribute(imObjectId, "source", source);
          // special case:
          // we have a gene model, and we differentiate from FB genes
          if ("MB9".equalsIgnoreCase(title)) {
              String id = fdat.getUniqueName();
              fdat.setUniqueName(title.concat("." + id));
          }
      }
      /**
       * Override method that adds completed features to featureMap.  Also put features that will
       * appear in multiple submissions in a map made available at the end of processing.
       * @param featureId the chado feature id
       * @param fdat feature information
       */
      protected void addToFeatureMap(Integer featureId, FeatureData fdat) {
          super.addToFeatureMap(featureId, fdat);
          // We know chromosomes will be common between submissions so add them here
          if (commonFeatureInterMineTypes.contains(fdat.getInterMineType())
                  && !commonFeaturesMap.containsKey(featureId)) {
              commonFeaturesMap.put(featureId, fdat);
          }
      }
      private List<String> getMatchTypes(Connection connection) throws SQLException {
          List<String> types = new ArrayList<String>();
          ResultSet res = getMatchTypesResultSet(connection);
          while (res.next()) {
              String type = res.getString("name");
              types.add(type.substring(0, type.indexOf('_')));
          }
          res.close();
          return types;
      }
      /**
       * Return the match types, used to determine which additional query to run
       * This is a protected method so that it can be overridden for testing
       * @param connection the database connection
       * @return the SQL result set
       * @throws SQLException if a database problem occurs
       */
      protected ResultSet getMatchTypesResultSet(Connection connection) throws SQLException {
          String query =
                  "SELECT name from cvterm where name like '%_match' ";
          LOG.info("executing: " + query);
          long bT = System.currentTimeMillis();
          Statement stmt = connection.createStatement();
          ResultSet res = stmt.executeQuery(query);
          LOG.debug("QUERY TIME feature match types: " + (System.currentTimeMillis() - bT));
          return res;
      }
      /**
       * Return the interesting feature (EST, UST, RST, other?) matches
       * from the featureloc and feature tables.
       *
       * feature<->featureloc<->match_feature<->featureloc<->feature
       * This is a protected method so that it can be overridden for testing
       * @param connection the database connection
       * @param featType the type of feature (EST,UST, etc)
       * @return the SQL result set
       * @throws SQLException if a database problem occurs
       */
      protected ResultSet getMatchLocResultSet(Connection connection, String featType)
          throws SQLException {
          String query =
                  "SELECT -1 AS featureloc_id, feat.feature_id, chrloc.fmin, "
                          + " chrloc.srcfeature_id AS srcfeature_id, chrloc.fmax, "
                          + " FALSE AS is_fmin_partial, featloc.strand "
                          + " FROM feature feat, featureloc featloc, cvterm featcv, feature mf, "
                          + " cvterm mfcv, featureloc chrloc, feature chr, cvterm chrcv "
                          + " WHERE feat.type_id = featcv.cvterm_id "
                          + " AND featcv.name = '" + featType + "' "
                          + " AND feat.feature_id = featloc.srcfeature_id "
                          + " AND featloc.feature_id = mf.feature_id "
                          + " AND mf.feature_id = chrloc.feature_id "
                          + " AND chrloc.srcfeature_id = chr.feature_id "
                          + " AND chr.type_id = chrcv.cvterm_id "
                          + " AND chrcv.name = 'chromosome' "
                          + " AND mf.type_id = mfcv.cvterm_id "
                          + " AND mfcv.name = '" + featType + "_match' "
                          + " AND feat.feature_id IN "
                          + " (select feature_id from " + SUBFEATUREID_TEMP_TABLE_NAME + " ) ";
          LOG.info("executing: " + query);
          long bT = System.currentTimeMillis();
          Statement stmt = connection.createStatement();
          ResultSet res = stmt.executeQuery(query);
          LOG.info("QUERY TIME feature " + featType + "_match: " + (System.currentTimeMillis() - bT));
          return res;
      }
      /**
       * {@inheritDoc}
       *
       * see FlyBaseProcessor for many more examples of configuration
       */
      @Override
      protected Map<MultiKey, List<ConfigAction>> getConfig(int taxonId) {
          MultiKeyMap map = config.get(new Integer(taxonId));
          if (map == null) {
              map = new MultiKeyMap();
              config.put(new Integer(taxonId), map);
              //feature configuration example: for features of class "Gene", from "modENCODE",
              //set the Gene.symbol to be the "name" field from the chado feature
              // map.put(new MultiKey("feature", "Gene", MODENCODE_SOURCE_NAME, "name"),
              // Arrays.asList(new SetFieldConfigAction("symbol")));
  
              // TODO: check possible conflicts with our sql matching
              // map.put(new MultiKey("relationship", "ESTMatch", "evidence_for_feature", "Intron"),
              //        Arrays.asList(new SetFieldConfigAction("intron")));
              map.put(new MultiKey("relationship", "ThreePrimeUTR", "adjacent_to", "CDS"),
                      Arrays.asList(new SetFieldConfigAction("CDS")));
              map.put(new MultiKey("relationship", "PolyASite",
                      "derives_from", "ThreePrimeRACEClone"),
                      Arrays.asList(new SetFieldConfigAction("threePrimeRACEClone")));
              map.put(new MultiKey("relationship", "ThreePrimeRST",
                      "derives_from", "ThreePrimeRACEClone"),
                      Arrays.asList(new SetFieldConfigAction("threePrimeRACEClone")));
              // evidence_for_feature
              map.put(new MultiKey("relationship", "OverlappingESTSet",
                      "evidence_for_feature", "TranscriptRegion"),
                      Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              map.put(new MultiKey("relationship", "ExperimentalFeature",
                      "evidence_for_feature", "Transcript"),
                      Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              map.put(new MultiKey("relationship", "ExperimentalFeature",
                      "evidence_for_feature", "Exon"),
                      Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              map.put(new MultiKey("relationship", "ExperimentalFeature",
                      "evidence_for_feature", "Intron"),
                      Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              map.put(new MultiKey("relationship", "ExperimentalFeature",
                      "evidence_for_feature", "ExonRegion"),
                      Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              map.put(new MultiKey("relationship", "Intron",
                      "evidence_for_feature", "PolypeptideRegion"),
                      Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              // partial_evidence_for_feature
              map.put(new MultiKey("relationship", "OverlappingESTSet",
                      "partial_evidence_for_feature", "MRNA"),
                      Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              map.put(new MultiKey("relationship", "TranscriptRegion",
                      "partial_evidence_for_feature", "MRNA"),
                      Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              // complete_evidence_for_feature
              map.put(new MultiKey("relationship", "ThreePrimeUST",
                      "complete_evidence_for_feature", "ThreePrimeUTR"),
                      Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              map.put(new MultiKey("relationship", "OverlappingESTSet",
                      "complete_evidence_for_feature", "Intron"),
                      Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              map.put(new MultiKey("relationship", "OverlappingESTSet",
                      "complete_evidence_for_feature", "PolyASite"),
                      Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              map.put(new MultiKey("relationship", "OverlappingESTSet",
                      "complete_evidence_for_feature", "SL1AcceptorSite"),
                      Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              map.put(new MultiKey("relationship", "OverlappingESTSet",
                      "complete_evidence_for_feature", "SL2AcceptorSite"),
                      Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              map.put(new MultiKey("relationship", "OverlappingESTSet",
                      "complete_evidence_for_feature", "TranscriptionEndSite"),
                      Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              map.put(new MultiKey("relationship", "OverlappingESTSet",
                      "complete_evidence_for_feature", "TSS"),
                      Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              // full_evidence_for_feature
              //          map.put(new MultiKey("relationship", "OverlappingESTSet",
              //                  "full_evidence_for_feature", "Gene"),
              //                  Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              map.put(new MultiKey("relationship", "OverlappingESTSet",
                      "full_evidence_for_feature", "MRNA"),
                      Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              map.put(new MultiKey("relationship", "TranscriptRegion",
                      "full_evidence_for_feature", "MRNA"),
                      Arrays.asList(new SetFieldConfigAction("supportedFeatures")));
              // additional properties
              map.put(new MultiKey("prop", "CDS", "status"),
                      Arrays.asList(new SetFieldConfigAction("status")));
              map.put(new MultiKey("prop", "CDS", "wormpep"),
                      Arrays.asList(new SetFieldConfigAction("wormpep")));
  