Browse Source

Adjusted how files are constructed to improve performance.

Stephen Ficklin 7 years ago
parent
commit
3be9ebbdc5

+ 1 - 1
tripal/api/tripal.collections.api.inc

@@ -184,5 +184,5 @@ function tripal_get_collection($values) {
 function tripal_create_collection_files($collection_id, TripalJob $job = NULL) {
   $collection = new TripalEntityCollection();
   $collection->load($collection_id);
-  $collection->writeAll($job);
+  $collection->write(NULL, $job);
 }

+ 7 - 3
tripal/api/tripal.entities.api.inc

@@ -158,7 +158,11 @@ function hook_tripal_default_title_format($bundle, $available_tokens) {
  * @param $field_ids
  *   A list of numeric field IDs that should be loaded.  The
  *   TripalField named 'content_type' is always automatically added.
- *
+ * @param $cache
+ *  When loading of entities they can be cached with Drupal for later
+ *  faster loading. However, this can cause memory issues when running
+ *  Tripal jobs that load lots of entities.  Caching of entities can
+ *  be disabled to improve memory performance by setting this to FALSE.
  * @return
  *   An array of entity objects indexed by their ids. When no results are
  *   found, an empty array is returned.
@@ -166,7 +170,7 @@ function hook_tripal_default_title_format($bundle, $available_tokens) {
  * @ingroup tripal_entities_api
  */
 function tripal_load_entity($entity_type, $ids = FALSE, $reset = FALSE,
-    $field_ids = array()) {
+    $field_ids = array(), $cache = TRUE) {
 
   // The $conditions is deprecated in the funtion arguments of entity_load
   // so it was removed from the parameters of this function as well. But
@@ -186,7 +190,7 @@ function tripal_load_entity($entity_type, $ids = FALSE, $reset = FALSE,
     $ec->resetCache();
   }
 
-  return $ec->load($ids, $conditions, $field_ids);
+  return $ec->load($ids, $conditions, $field_ids, $cache);
 }
 /**
  * Retrieves a TripalTerm entity that matches the given arguments.

+ 192 - 46
tripal/includes/TripalEntityCollection.inc

@@ -74,8 +74,7 @@ class TripalEntityCollection {
       foreach ($this->downloaders as $class_name => $label) {
         tripal_load_include_downloader_class($class_name);
         $outfile = $this->getOutfile($class_name);
-        $downloader = new $class_name($this->bundles, $this->ids, $this->fields,
-            $outfile, $this->getUserID(), $this->collection_id);
+        $downloader = new $class_name($this->collection_id, $outfile);
         $downloader->delete();
       }
 
@@ -136,11 +135,11 @@ class TripalEntityCollection {
         $bundle_name = $bundle->bundle_name;
         if (strpos($bundle->bundle_name, 'bio_data_') !== 0) {
           $ids[$bundle_name] = $this->getEntityIDs($bundle_name);
-          $fields[$bundle_name] = $this->getFields($bundle_name);
+          $fields[$bundle_name] = $this->getFieldIDs($bundle_name);
         }
         $bundle_name = $bundle->bundle_name;
         $ids[$bundle_name] = $this->getEntityIDs($bundle_name);
-        $fields[$bundle_name] = $this->getFields($bundle_name);
+        $fields[$bundle_name] = $this->getFieldIDs($bundle_name);
       }
       $this->ids = $ids;
       $this->fields = $fields;
@@ -149,7 +148,7 @@ class TripalEntityCollection {
       if (!empty($this->bundles)) {
         $bundle_name = $this->bundles[0]->bundle_name;
         $this->ids = $this->getEntityIDs($bundle_name);
-        $this->fields = $this->getFields($bundle_name);
+        $this->fields = $this->getFieldIDs($bundle_name);
       }
     }
     // Iterate through the fields and find out what download formats are
@@ -460,26 +459,22 @@ class TripalEntityCollection {
    *   An array of numeric entity IDs.
    */
   public function getEntityIDs($bundle_name) {
+    $entity_ids = array();
+
+    if (!$bundle_name) {
+      throw new Exception('Please provide the $bundle_name argument for the TripalEntityCollection::getEntityIDs() function.');
+    }
+
+    // Get the IDs for the entities for tihs bundle collection.
     $collection_id = $this->collection_id;
-    // Return the bundles from the collection_bundle table.
-    $result = db_select('tripal_collection_bundle')
+    $ids = db_select('tripal_collection_bundle')
       ->fields('tripal_collection_bundle', array('ids'))
       ->condition('collection_id', $collection_id, '=')
       ->condition('bundle_name', $bundle_name, '=')
       ->execute()
-      ->fetchAll();
-
-    // Unserialize the array of standard class objects.
-    $unserialized_result = [];
-    foreach ($result as $id_list) {
-      $unserialized_id_list = unserialize($id_list->ids);
-
-      foreach ($id_list as $item) {
-        $unserialized_result[] = $unserialized_id_list;
-      }
-    }
+      ->fetchField();
 
-    return $unserialized_result;
+    return unserialize($ids);
   }
 
   /**
@@ -488,10 +483,11 @@ class TripalEntityCollection {
    * @return
    *   An array of numeric field IDs.
    */
-  public function getFields($bundle_name) {
-    $collection_id = $this->collection_id;
+  public function getFieldIDs($bundle_name) {
+    $field_ids = array();
 
-    // Get the fields from the collection_bundle table.
+    // Get the IDs for the fields for this bundle collection.
+    $collection_id = $this->collection_id;
     $result = db_select('tripal_collection_bundle')
       ->fields('tripal_collection_bundle', array('fields'))
       ->condition('collection_id', $collection_id, '=')
@@ -621,20 +617,6 @@ class TripalEntityCollection {
     return FALSE;
   }
 
-  /**
-   * Writes the collection to all file downloadable formats.
-   *
-   * @param $job
-   *    If this function is run as a Tripal Job then this argument can be
-   *    set to the Tripaljob object for keeping track of progress.
-   * @throws Exception
-   */
-  public function writeAll(TripalJob $job = NULL) {
-    foreach ($this->downloaders as $class_name => $label) {
-      $this->write($class_name, $job);
-    }
-  }
-
   /**
    * Retrieves the URL for the downloadable file.
    *
@@ -671,31 +653,195 @@ class TripalEntityCollection {
   }
 
   /**
-   * Writes the collection to a file.
+   * Writes the collection to a file using a given formatter.
    *
    * @param formatter
    *   The name of the formatter class to use (e.g. TripalTabDownloader). The
-   *   formatter must be compatible with the data collection.
+   *   formatter must be compatible with the data collection.  If no
+   *   formatter is supplied then all file formats supported by this
+   *   data collection will be created.
    * @param $job
    *    If this function is run as a Tripal Job then this argument can be
    *    set to the Tripaljob object for keeping track of progress.
    * @throws Exception
    */
-  public function write($formatter, TripalJob $job = NULL) {
-    if (!$this->isFormatterCompatible($formatter)) {
-      throw new Exception(t('The formatter, "@formatter", is not compatible with this data collection.', array('@formatter' => $formatter)));
+  public function write($formatter = NULL, TripalJob $job = NULL) {
+
+    $downloaders = array();
 
+    // Initialize the downloader classes and initialize the files for writing.
+    $formatters = $this->getDownloadFormatters();
+    foreach ($formatters as $class => $label) {
+      if (!$this->isFormatterCompatible($class)) {
+        throw new Exception(t('The formatter, "@formatter", is not compatible with this data collection.', array('@formatter' => $formatter)));
+      }
+      if (!tripal_load_include_downloader_class($class)) {
+        throw new Exception(t('Cannot find the formatter named "@formatter".', array('@formatter', $formatter)));
+      }
+      $outfile = $this->getOutfile($class);
+      if (!$formatter or ($formatter == $class)) {
+        $downloaders[$class] = new $class($this->collection_id, $outfile);
+        $downloaders[$class]->writeInit($job);
+        if ($job) {
+          $job->logMessage("Writing " . lcfirst($class::$full_label) . " file.");
+        }
+      }
     }
 
-    if (!tripal_load_include_downloader_class($formatter)) {
-      throw new Exception(t('Cannot find the formatter named "@formatter".', array('@formatter', $formatter)));
+    // Count the total number of entities
+    $total_entities = 0;
+    $bundle_collections = $this->collection_bundles;
+    foreach ($this->bundles as $bundle) {
+      $bundle_name = $bundle->bundle_name;
+      $entity_ids  = $this->getEntityIDs($bundle_name);
+      $total_entities += count($entity_ids);
+    }
+    if ($job) {
+      $job->setTotalItems($total_entities);
     }
 
-    $outfile = $this->getOutfile($formatter);
+    // Next load the entities and write them to the files.
+    foreach ($this->bundles as $bundle) {
+      $bundle_name = $bundle->bundle_name;
+      $site_id = $bundle->site_id;
+      $entity_ids  = $this->getEntityIDs($bundle_name);
+      $field_ids = $this->getFieldIDs($bundle_name);
 
-    $downloader = new $formatter($this->collection_id, $outfile);
-    $downloader->write($job);
+      foreach ($entity_ids as $entity_id) {
+        $num_handled++;
 
+        if ($job) {
+          $job->setItemsHandled($num_handled);
+        }
+
+        // if we have a site_id then we need to get the entity from the
+        // remote service. Otherwise create the entity from the local system.
+        $entity = NULL;
+        if ($site_id) {
+          $entity = $this->loadRemoteEntity($entity_id, $site_id, $bundle_name);
+          if (!$entity) {
+            continue;
+          }
+        }
+        else {
+          $result = tripal_load_entity('TripalEntity', array($entity_id), FALSE, $field_ids, FALSE);
+          $entity = $result[$entity_id];
+        }
+
+        if (!$entity) {
+          continue;
+        }
+
+        // Write the same entity to all the formatters that are supported.
+        foreach ($downloaders as $my_formatter => $downloader) {
+          $downloader->writeEntity($entity, $job);
+        }
+      }
+    }
+
+    // Now close up all the files
+    foreach ($downloaders as $my_formatter => $downloader) {
+      $downloader->writeDone($job);
+    }
   }
 
+  /**
+   * Build and return a fake entity from a remote site using
+   * tripal web services calls.
+   *
+   * @param $remote_ids
+   *   Array of the remote ids.
+   *
+   * @param $site_id
+   *   Unique site id assigned in the tripal_sites table when
+   *   a new site is created via the web services interface.
+   *
+   * @param $remote_fields
+   *   Array of the remote fields.
+   *
+   * @param $bundle_name
+   *   Bundle name of the remote field, in this instance it will be
+   *   the accession of the field.
+   *
+   * @return $fake_tripal_entity
+   *    This is a fake entity structured to allow the format
+   *    entity function to process and return the info.
+   */
+  protected function loadRemoteEntity($remote_id, $site_id, $bundle_name) {
+
+    // Get the site documentation
+    $site = empty($site_id) ? 'local' : $site_id;
+    $site_doc = $this->retrieveRemoteAPIDoc($site_id);
+
+    // Get the remote entity and create the fake entity.
+    $query = $bundle_name . '/' . $remote_id;
+    $remote_entity = tripal_query_remote_site($site_id, $query);
+    if (!$remote_entity) {
+      return FALSE;
+    }
+
+    // Start building the fake id.
+    $entity = new stdClass();
+    $entity->entityType = 'TripalEntity';
+    $entity->entityInfo = [];
+    $entity->id = $remote_id;
+    $entity->type = 'TripalEntity';
+    $entity->bundle = $bundle_name;
+    $entity->site_id = $site_id;
+
+    // Get the context JSON for this remote entity, we'll use it to map
+    // the properties to the correct fields.
+    $context = drupal_http_request($remote_entity['@context']);
+    $context = drupal_json_decode($context->data);
+    $context = $context['@context'];
+
+    // Iterate through the fields that are printable and get those values
+    // from the results.
+    foreach ($this->printable_fields as $accession => $label) {
+      $field_id = $this->fields2terms[$site][$bundle_name]['by_accession'][$accession];
+
+      // If the field isn't part of this bundle then skip it.
+      if (!$field_id) {
+        continue;
+      }
+
+      $field = $this->fields[$site][$bundle_name][$field_id]['field'];
+      $instance = $this->fields[$site][$bundle_name][$field_id]['instance'];
+      $field_name = $field['field_name'];
+      $accession = $instance['settings']['term_vocabulary'] . ':' . $instance['settings']['term_accession'];
+
+      // Get the key for this field from the context.
+      $field_key = $accession;
+      foreach ($context as $k => $v) {
+        if (!is_array($v)) {
+        }
+        if (!is_array($v) and $v == $accession) {
+          $field_key = $k;
+        }
+      }
+
+      // If the field is not in this remote bundle then add an empty value for
+      // it.
+      if (!$field_key) {
+        $entity->{$field_name}['und'][0]['value'] = '';
+        continue;
+      }
+
+      // If the key is for a field that is not "auto attached' then we need
+      // to get that field through a separate call.
+      $needs_query = FALSE;
+      if (array_key_exists($field_name, $context) and is_array($context[$field_name]) and
+          array_key_exists('@type', $context[$field_name]) and $context[$field_name]['@type'] == '@id'){
+            $needs_query = TRUE;
+      }
+
+      $value = '';
+      if (!$needs_query) {
+        $value = $remote_entity[$field_key];
+      }
+      $entity->{$field_name}['und'][0]['value'] = $value;
+    }
+
+    return $entity;
+  }
 }

+ 8 - 3
tripal/includes/TripalEntityController.inc

@@ -437,8 +437,13 @@ class TripalEntityController extends EntityAPIController {
    *  The list of key/value filters for querying the entity.
    * @param $field_ids
    *  The list of numeric field IDs for fields that should be attached.
+   * @param $cache
+   *  When loading of entities they can be cached with Drupal for later
+   *  faster loading. However, this can cause memory issues when running
+   *  Tripal jobs that load lots of entities.  Caching of entities can
+   *  be disabled to improve memory performance by setting this to FALSE.
    */
-  public function load($ids = array(), $conditions = array(), $field_ids = array()) {
+  public function load($ids = array(), $conditions = array(), $field_ids = array(), $cache = TRUE) {
 
     $entities = array();
 
@@ -529,10 +534,10 @@ class TripalEntityController extends EntityAPIController {
       EntityCacheControllerHelper::entityCacheSet($this, $queried_entities);
     }
 
-    if ($this->cache) {
+    if ($this->cache and $cache) {
       // Add entities to the cache if we are not loading a revision.
       if (!empty($queried_entities) && !$revision_id) {
-        $this->cacheSet($queried_entities);
+          $this->cacheSet($queried_entities);
 
         // Remember if we have cached all entities now.
         if (!$conditions && $ids === FALSE) {

+ 1 - 1
tripal/includes/TripalFieldDownloaders/TripalCSVDownloader.inc

@@ -68,7 +68,7 @@ class TripalCSVDownloader extends TripalFieldDownloader {
         }
         else {
           if (array_key_exists('rdfs:label', $entity->{$field_name}['und'][0]['value'])) {
-            $row[] = '"' . $tabs . strip_tags($entity->{$field_name}['und'][0]['value']['rdfs:label']) . '"';
+            $row[] = '"' . strip_tags($entity->{$field_name}['und'][0]['value']['rdfs:label']) . '"';
           }
           else {
             $row[] = '';

+ 80 - 145
tripal/includes/TripalFieldDownloaders/TripalFieldDownloader.inc

@@ -109,6 +109,11 @@ abstract class TripalFieldDownloader {
   protected $fields = array();
 
 
+  /**
+   * The file handle for an opeend file using during writing.
+   */
+  protected $fh;
+
   /**
    * Constructs a new instance of the TripalFieldDownloader class.
    *
@@ -219,77 +224,53 @@ abstract class TripalFieldDownloader {
   }
 
   /**
-   * Creates the downloadable file.
    *
-   * @param $job
-   *    If this function is run as a Tripal Job then this argument can be
-   *    set to the Tripaljob object for keeping track of progress.
+   * @param TripalJob $job
    */
-  public function write(TripalJob $job = NULL) {
+  public function writeInit(TripalJob $job = NULL) {
 
     $user = user_load($this->collection->uid);
 
-    $fh = fopen(drupal_realpath($this->outfile), "w");
-
-    if (!$fh) {
+    $this->fh = fopen(drupal_realpath($this->outfile), "w");
+    if (!$this->fh) {
       throw new Exception("Cannout open collection file: " . $this->outfile);
     }
 
+    // Add the headers to the file.
     $headers = $this->getHeader();
     if ($headers) {
       foreach ($headers as $line) {
-        fwrite($fh, $line . "\r\n");
+        fwrite($this->fh, $line . "\r\n");
       }
     }
+  }
 
-    // Count the total number of entities
-    $total_entities = 0;
-    $bundle_collections = $this->collection_bundles;
-    foreach ($bundle_collections as $bundle_collection) {
-      $total_entities += count($bundle_collection->ids);
-    }
-    if ($job) {
-      $job->setTotalItems($total_entities);
+  /**
+   * Write a single entity to the file.
+   *
+   * Before calling this function call the initWrite() function to
+   * establish the file and write headers.
+   *
+   * @param $entity
+   *   The Entity to write.
+   * @param TripalJob $job
+   */
+  public function writeEntity($entity, TripalJob $job = NULL){
+    $lines = $this->formatEntity($entity);
+    foreach ($lines as $line) {
+      fwrite($this->fh, $line . "\r\n");
     }
+  }
 
-    $num_handled = 0;
-    foreach ($bundle_collections as $bundle_collection) {
-      $collection_bundle_id = $bundle_collection->collection_bundle_id;
-      $bundle_name = $bundle_collection->bundle_name;
-      $entity_ids = $bundle_collection->ids;
-      $fields = $bundle_collection->fields;
-      $site_id = $bundle_collection->site_id;
-
-      foreach ($entity_ids as $entity_id) {
-        $num_handled++;
-        if ($job) {
-          $job->setItemsHandled($num_handled);
-        }
-
-        // if we have a site_id then we need to get the entity from the
-        // remote service. Otherwise create the entity from the local system.
-        if ($site_id) {
-          $entity = $this->loadRemoteEntity($entity_id, $site_id, $bundle_name);
-          if (!$entity) {
-            continue;
-          }
-        }
-        else {
-          $result = tripal_load_entity('TripalEntity', array($entity_id), FALSE, $fields);
-          $entity = $result[$entity_id];
-        }
-
-        if (!$entity) {
-          continue;
-        }
+  /**
+   * Closes the output file once writing of all entities is completed.
+   *
+   * @param TripalJob $job
+   */
+  public function writeDone(TripalJob $job = NULL) {
+    fclose($this->fh);
 
-        $lines = $this->formatEntity($entity);
-        foreach ($lines as $line) {
-          fwrite($fh, $line . "\r\n");
-        }
-      }
-    }
-    fclose($fh);
+    $user = user_load($this->collection->uid);
 
     $file = new stdClass();
     $file->uri = $this->outfile;
@@ -315,7 +296,6 @@ abstract class TripalFieldDownloader {
       $file = file_load($fid);
     }
 
-
     // We use the fid for the last argument because these files
     // aren't really associated with any entity, but we need a value./
     // But, only add the usage if it doens't already exists.
@@ -331,113 +311,68 @@ abstract class TripalFieldDownloader {
     }
   }
 
-  /**
-   * Setups a download stream for the file.
-   */
-  public function download() {
-
-  }
 
   /**
-   * Build and return a fake entity from a remote site using
-   * tripal web services calls.
-   *
-   * @param $remote_ids
-   *   Array of the remote ids.
-   *
-   * @param $site_id
-   *   Unique site id assigned in the tripal_sites table when
-   *   a new site is created via the web services interface.
-   *
-   * @param $remote_fields
-   *   Array of the remote fields.
-   *
-   * @param $bundle_name
-   *   Bundle name of the remote field, in this instance it will be
-   *   the accession of the field.
+   * Creates the downloadable file.
    *
-   * @return $fake_tripal_entity
-   *    This is a fake entity structured to allow the format
-   *    entity function to process and return the info.
+   * @param $job
+   *    If this function is run as a Tripal Job then this argument can be
+   *    set to the Tripaljob object for keeping track of progress.
    */
-  protected function loadRemoteEntity($remote_id, $site_id, $bundle_name) {
-
-    // Get the site documentation
-    $site = empty($site_id) ? 'local' : $site_id;
-    $site_doc = $this->retrieveRemoteAPIDoc($site_id);
+  public function write(TripalJob $job = NULL) {
 
-    // Get the remote entity and create the fake entity.
-    $query = $bundle_name . '/' . $remote_id;
-    $remote_entity = tripal_query_remote_site($site_id, $query);
-    if (!$remote_entity) {
-      return FALSE;
-    }
+    $this->initWrite($job);
 
-    // Start building the fake id.
-    $entity = new stdClass();
-    $entity->entityType = 'TripalEntity';
-    $entity->entityInfo = [];
-    $entity->id = $remote_id;
-    $entity->type = 'TripalEntity';
-    $entity->bundle = $bundle_name;
-    $entity->site_id = $site_id;
-
-    // Get the context JSON for this remote entity, we'll use it to map
-    // the properties to the correct fields.
-    $context = drupal_http_request($remote_entity['@context']);
-    $context = drupal_json_decode($context->data);
-    $context = $context['@context'];
-
-    // Iterate through the fields that are printable and get those values
-    // from the results.
-    foreach ($this->printable_fields as $accession => $label) {
-      $field_id = $this->fields2terms[$site][$bundle_name]['by_accession'][$accession];
-
-      // If the field isn't part of this bundle then skip it.
-      if (!$field_id) {
-        continue;
-      }
+    $num_handled = 0;
+    foreach ($bundle_collections as $bundle_collection) {
+      $collection_bundle_id = $bundle_collection->collection_bundle_id;
+      $bundle_name = $bundle_collection->bundle_name;
+      $entity_ids = $bundle_collection->ids;
+      $fields = $bundle_collection->fields;
+      $site_id = $bundle_collection->site_id;
 
-      $field = $this->fields[$site][$bundle_name][$field_id]['field'];
-      $instance = $this->fields[$site][$bundle_name][$field_id]['instance'];
-      $field_name = $field['field_name'];
-      $accession = $instance['settings']['term_vocabulary'] . ':' . $instance['settings']['term_accession'];
+      foreach ($entity_ids as $entity_id) {
+        $num_handled++;
+        if ($job) {
+          $job->setItemsHandled($num_handled);
+        }
 
-      // Get the key for this field from the context.
-      $field_key = $accession;
-      foreach ($context as $k => $v) {
-        if (!is_array($v)) {
+        // if we have a site_id then we need to get the entity from the
+        // remote service. Otherwise create the entity from the local system.
+        if ($site_id) {
+          $entity = $this->loadRemoteEntity($entity_id, $site_id, $bundle_name);
+          if (!$entity) {
+            continue;
+          }
         }
-        if (!is_array($v) and $v == $accession) {
-          $field_key = $k;
+        else {
+          $result = tripal_load_entity('TripalEntity', array($entity_id), FALSE, $fields, FALSE);
+          $entity = $result[$entity_id];
         }
-      }
-
-      // If the field is not in this remote bundle then add an empty value for
-      // it.
-      if (!$field_key) {
-        $entity->{$field_name}['und'][0]['value'] = '';
-        continue;
-      }
 
-      // If the key is for a field that is not "auto attached' then we need
-      // to get that field through a separate call.
-      $needs_query = FALSE;
-      if (array_key_exists($field_name, $context) and is_array($context[$field_name]) and
-          array_key_exists('@type', $context[$field_name]) and $context[$field_name]['@type'] == '@id'){
-        $needs_query = TRUE;
-      }
+        if (!$entity) {
+          continue;
+        }
 
-      $value = '';
-      if (!$needs_query) {
-        $value = $remote_entity[$field_key];
+         $lines = $this->formatEntity($entity);
+         foreach ($lines as $line) {
+           fwrite($fh, $line . "\r\n");
+         }
       }
-      $entity->{$field_name}['und'][0]['value'] = $value;
     }
 
-    return $entity;
+    $this->finishWrite($job);
+  }
+
+  /**
+   * Setups a download stream for the file.
+   */
+  public function download() {
+
   }
 
+
+
   /**
    * Retrieves the vocabulary for a remote Tripal web service.
    *

+ 2 - 1
tripal/includes/TripalFieldDownloaders/TripalTabDownloader.inc

@@ -63,7 +63,8 @@ class TripalTabDownloader extends TripalFieldDownloader {
          }
          else {
            if (array_key_exists('rdfs:label', $entity->{$field_name}['und'][0]['value'])) {
-             $row[] = $tabs . strip_tags($entity->{$field_name}['und'][0]['value']['rdfs:label']);
+             $label = $entity->{$field_name}['und'][0]['value']['rdfs:label'];
+             $row[] = strip_tags($label);
            }
            else {
              $row[] = '';

+ 4 - 4
tripal_chado/includes/TripalImporter/GFF3Importer.inc

@@ -1071,13 +1071,13 @@ class GFF3Importer extends TripalImporter {
     if (!$type_id) {
       $result = chado_select_record('feature', array('type_id'), $values);
       if (count($result) > 1) {
-        $this->logMessage("Cannot find feature type for, '%subject' , in 'derives_from' relationship. Multiple matching features exist with this uniquename.",
-            array('%subject' => $object), TRIPAL_WARNING);
+        $this->logMessage("Cannot find feature type for, '!subject' , in 'derives_from' relationship. Multiple matching features exist with this uniquename.",
+            array('!subject' => $object), TRIPAL_WARNING);
         return;
       }
       else if (count($result) == 0) {
-        $this->logMessage("Cannot find feature type for, '%subject' , in 'derives_from' relationship.",
-            array('%subject' => $object), TRIPAL_WARNING);
+        $this->logMessage("Cannot find feature type for, '!subject' , in 'derives_from' relationship.",
+            array('!subject' => $object), TRIPAL_WARNING);
         return '';
       }
       else {