Browse Source

Switched both nucleotide and protein BLAST forms over to use Tripal jobs.

Lacey Sanderson 10 years ago
parent
commit
a6d80bb2ba

+ 42 - 0
api/blast_ui.api.inc

@@ -34,3 +34,45 @@ function get_blast_database_options($type) {
 
   return $options;
 }
+
+/**
+ * Run BLAST (should be called from the command-line)
+ *
+ * @param $program
+ *   Which BLAST program to run (ie: 'blastn', 'tblastn', tblastx', 'blastp','blastx')
+ * @param $query
+ *   The full path and filename of the query FASTA file
+ * @param $database
+ *   The full path and filename prefix (excluding .nhr, .nin, .nsq, etc.)
+ * @param $output_filename
+ *   The filename (not including path) to give the results
+ * @param $options
+ *   An array of additional option where the key is the name of the option used by
+ *   BLAST (ie: 'num_alignments') and the value is relates to this particular
+ *   BLAST job (ie: 250)
+ */
+function run_BLAST_tripal_job($program, $query, $database, $output_file, $options, $job_id = NULL) {
+
+  $output_file = 'sites/default/files/' . $output_file;
+
+  print "\nExecuting $program\n\n";
+  print "Query: $query\n";
+  print "Database: $database\n";
+  print "Results File: $output_file\n";
+
+  print "Options:\n";
+
+  $blast_cmd = "$program -query $query -db $database -out $output_file -outfmt=5";
+  if (!empty($options)) {
+    foreach ($options as $opt => $val) {
+      print "\t$opt: $val\n";
+      $blast_cmd .= " -$opt $val";
+    }
+  }
+
+  print "\nExecuting the following BLAST command:\n" . $blast_cmd . "\n";
+
+  system($blast_cmd);
+
+  print "\nDone!\n";
+}

+ 36 - 34
blast_ui.module

@@ -53,7 +53,7 @@ function blast_ui_menu() {
 
   // BLAST Results page
   $items['blast/report/%'] = array(
-    'title' => 'BLAST result:',
+    'title' => 'BLAST Results',
     'page callback' => 'show_blast_output',
     'page arguments' => array(2),
     'access arguments' => array('access content'),
@@ -77,6 +77,12 @@ function blast_ui_theme() {
     'path' => "$path/theme",
   );
 
+  // Displays the BLAST results for each job
+  $items['blast_report_pending'] = array(
+    'template' => 'blast_report_pending',
+    'path' => "$path/theme",
+  );
+
   // Themes the alignments in a BLAST result display
   $items['blast_report_alignment_row'] = array(
     'template' => 'blast_report_alignment_row',
@@ -90,46 +96,42 @@ function blast_ui_theme() {
 /**
  * Facilitate presenting the result of the blast search
  *
- * @param $args
- *  A string containing name of the blast output file.
+ * @param $job_id
+ *  The tripal job_id of the BLAST job previously submitted
  *
  * @return $result
  *  Return HTML output of the BLAST results to be displayed to the user
  *
  */
-function show_blast_output($args = 'all') {
-
-  // Double-check that there are no directory slashes in the path since this could be used
-  // present a security risk (ie: if the path contained ../../../etc/someconfig.file this
-  // we don't want to process or display anything.
-  if (preg_match('/^[^\/]*$/',$args)) {
-
-    // Since the blast results are in the files directory
-    // we can use public:// to get around hard-coding the full path
-    $full_path_filename = 'public://'.$args;
-
-    // check that the XML file exists before trying to display results
-    if (file_exists($full_path_filename)) {
-      // Use the show_blast_output.tpl.php to generate the HTML
-	    $result = theme('show_blast_report');
-    }
-    else {
-      // If the file doesn't exist then throw a tripal error
-      // as well as displaying an error to the user
-      tripal_report_error(
-        'blast_ui',
-        TRIPAL_ERROR,
-        'Unable to open blast results file (%file)',
-        array('%file' => $full_path_filename)
-      );
-      $result = '<p>An error was encountered while trying to process your blast results</p>';
-    }
+function show_blast_output($job_id) {
+
+  // BLASTs are run as a Tripal job. As such we need to determine whether the current
+  // BLAST is in the queue, running or complete in order to determine what to show the user
+  $job = tripal_get_job($job_id);
+
+  // 1) Job is in the Queue
+  if ($job->start_time === NULL AND $job->end_time == NULL) {
+
+    return theme('blast_report_pending', array('status_code' => 0, 'status' => 'Pending'));
   }
+  // 2) Job has been Cancelled
+  elseif ($job->status == 'Cancelled') {
+
+    return theme('blast_report_pending', array('status_code' => 999, 'status' => 'Cancelled'));
+  }
+  // 3) Job is Complete
+  elseif ($job->end_time !== NULL) {
+
+    // Return the Results :)
+    return theme('show_blast_report', array('job_id' => $job_id));
+
+  }
+  // 4) Job is in Progress
   else {
-    // If there are directory slashes in the path then just display an error to the user
-    // rather than risk the security of the server
-    $result = '<p>An error was encountered while trying to process your blast results</p>';
+
+    return theme('blast_report_pending', array('status_code' => 1, 'status' => 'Running'));
   }
 
-  return $result;
+
+  return '';
 }

+ 46 - 18
includes/blast_ui.blastn.inc

@@ -253,10 +253,6 @@ function blast_nucleotide_form_validate($form, &$form_state) {
  */
 function blast_nucleotide_form_submit($form, &$form_state) {
 
-  // SHOULDN'T BE HARDCODED!!
-  exec("export BLASTDB=/home/Sequences/blast_dbs/");
-
-
   $eVal = $form_state['values']['eVal'];
 
   $trgtKey = $form_state['values']['maxTarget'];
@@ -343,30 +339,62 @@ function blast_nucleotide_form_submit($form, &$form_state) {
   // If the BLAST database was uploaded then use it to run the BLAST
   if ( $form_state['dbFlag'] == 'upQuery') {
 
-     $subjectSeq = $form_state['upDB_path'];
-     $subSeqOut = drupal_basename($form_state['upDB_path']) . rand(0, 10000);
+    // Since we only support using the -db flag (not -subject) we need to create a
+    // blast database for the FASTA uploaded.
+    // NOTE: We can't support subject because we need to generate the ASN.1+ format
+    // to provide multiple download type options from the same BLAST
+    $blastdb_with_path = $form_state['upDB_path'];
+    system("makeblastdb -in $blastdb_with_path -dbtype nucl -parse_seqids");
 
-     $blast_subj_cmd = "blastn -query $query -subject $subjectSeq -out sites/default/files/$subSeqOut.blastn.html -evalue $eVal -word_size $wordSize -gapopen $gapOpen -gapextend $gapExtend -penalty $penalty -reward $reward -num_alignments 100 -html";
-     system($blast_subj_cmd);
+    //$blast_subj_cmd = "blastn -query $query -subject $subjectSeq -out sites/default/files/$subSeqOut.blastn.html -evalue $eVal -word_size $wordSize -gapopen $gapOpen -gapextend $gapExtend -penalty $penalty -reward $reward -num_alignments 100 -html";
+    //system($blast_subj_cmd);
 
   }
   // Otherwise, we are using one of the website provided BLAST databases so form the
   // BLAST command accordingly
   elseif ($form_state['dbFlag'] == 'blastdb') {
 
-     $selected_db = $form_state['values']['SELECT_DB'];
-     $blastdb_node = node_load($selected_db);
-     $blastdb_path = $blastdb_node->db_path;
-     $blastdb_human_name = $form['DB']['SELECT_DB']['#options'][$selected_db];
-     $subSeqOut = str_replace(' ','_', $blastdb_human_name) . rand(0, 10000);
+    $selected_db = $form_state['values']['SELECT_DB'];
+    $blastdb_node = node_load($selected_db);
+    $blastdb_with_path = $blastdb_node->db_path;
 
-     $blast_db_cmd = "blastn -task blastn -query $query -db $blastdb_path -out sites/default/files/$subSeqOut.blastn.xml -evalue $eVal -word_size $wordSize -gapopen $gapOpen -gapextend $gapExtend -penalty $penalty -reward $reward -num_alignments 100 -outfmt=5";
-     system($blast_db_cmd,$input);
   }
 
-  // Redirect to the BLAST results page
-  $path = "$subSeqOut.blastn.xml";
-  drupal_goto("blast/report/$path");
+  // Actually submit the BLAST Tripal Job
+  // NOTE: Tripal jobs needs to be executed from the command-line before it will be run!!
+  $blastdb_with_suffix = $blastdb_with_path . '.nsq';
+  if (is_readable($blastdb_with_suffix)) {
+    global $user;
+    $output_filestub = date('YMd_His');
+    $job_args = array(
+      'program' => 'blastn',
+      'query' => $query,
+      'database' => $blastdb_with_path,
+      'output_filename' => $output_filestub . ".blast.xml",
+      'options' => array(
+        'evalue' => $eVal,
+        'word_size' => $wordSize,
+        'gapopen' => $gapOpen,
+        'gapextend' => $gapExtend,
+        'penalty' =>  $penalty,
+        'reward' => $reward
+      )
+    );
+    $job_id = tripal_add_job("BLAST (blastn): $query",'blast_job','run_BLAST_tripal_job', $job_args, $user->uid);
+
+    // Redirect to the BLAST results page
+    drupal_goto("blast/report/$job_id");
+  }
+  else {
+    $dbfile_uploaded_msg = ($form_state['dbFlag'] == 'upQuery') ? 'The BLAST database was submitted via user upload.' : 'Existing BLAST Database was chosen';
+    tripal_report_error(
+      'blast_ui',
+      TRIPAL_ERROR,
+      "BLAST database %db unaccessible. $dbfile_uploaded_msg",
+      array('%db' => $blastdb_with_path)
+    );
+    drupal_set_message('BLAST database unaccessible. Please contact the site administrator.','error');
+  }
 }
 
 

+ 41 - 13
includes/blast_ui.blastp.inc

@@ -639,11 +639,12 @@ function blast_protein_form_submit($form, &$form_state) {
   // If the BLAST database was uploaded then use it to run the BLAST
   if ($form_state['dbFlag'] == 'upQuery') {
 
-     $subjectSeq = $form_state['upDB_path'];
-     $subSeqOut = drupal_basename($form_state['upDB_path']) . rand(0, 10000);
-
-     $blast_subj_cmd = "blastp -task blastp -query $query -subject $subjectSeq -out sites/default/files/$subSeqOut.blastp.html";
-     system($blast_subj_cmd);
+    // Since we only support using the -db flag (not -subject) we need to create a
+    // blast database for the FASTA uploaded.
+    // NOTE: We can't support subject because we need to generate the ASN.1+ format
+    // to provide multiple download type options from the same BLAST
+    $blastdb_with_path = $form_state['upDB_path'];
+    system("makeblastdb -in $blastdb_with_path -dbtype prot -parse_seqids");
 
   }
   // Otherwise, we are using one of the website provided BLAST databases so form the
@@ -652,17 +653,44 @@ function blast_protein_form_submit($form, &$form_state) {
 
      $selected_db = $form_state['values']['SELECT_DB'];
      $blastdb_node = node_load($selected_db);
-     $blastdb_path = $blastdb_node->db_path;
-     $blastdb_human_name = $form['DB']['SELECT_DB']['#options'][$selected_db];
-     $subSeqOut = str_replace(' ','_', $blastdb_human_name) . rand(0, 10000);
+     $blastdb_with_path = $blastdb_node->db_path;
 
-     $blast_db_cmd = "blastp -task blastp -query $query -db $blastdb_path -out sites/default/files/$subSeqOut.blastp.html -evalue $eVal -word_size $wordSize -gapopen $gapOpen -gapextend $gapExtend -matrix $matrix -num_alignments 100 -html";
-     system($blast_db_cmd,$input);
   }
 
-  // Redirect to the BLAST Results page
-  $path = "$subSeqOut.blastp.html";
-  drupal_goto("blast/report/$path");
+  // Actually submit the BLAST Tripal Job
+  // NOTE: Tripal jobs needs to be executed from the command-line before it will be run!!
+  $blastdb_with_suffix = $blastdb_with_path . '.psq';
+  if (is_readable($blastdb_with_suffix)) {
+    global $user;
+    $output_filestub = date('YMd_His');
+    $job_args = array(
+      'program' => 'blastp',
+      'query' => $query,
+      'database' => $blastdb_with_path,
+      'output_filename' => $output_filestub . ".blast.xml",
+      'options' => array(
+        'evalue' => $eVal,
+        'word_size' => $wordSize,
+        'gapopen' => $gapOpen,
+        'gapextend' => $gapExtend,
+        'matrix' => $matrix
+      )
+    );
+    $job_id = tripal_add_job("BLAST (blastp): $query",'blast_job','run_BLAST_tripal_job', $job_args, $user->uid);
+
+    // Redirect to the BLAST results page
+    drupal_goto("blast/report/$job_id");
+  }
+  else {
+    $dbfile_uploaded_msg = ($form_state['dbFlag'] == 'upQuery') ? 'The BLAST database was submitted via user upload.' : 'Existing BLAST Database was chosen';
+    tripal_report_error(
+      'blast_ui',
+      TRIPAL_ERROR,
+      "BLAST database %db unaccessible. $dbfile_uploaded_msg",
+      array('%db' => $blastdb_with_path)
+    );
+    drupal_set_message('BLAST database unaccessible. Please contact the site administrator.','error');
+  }
 }
 
 

BIN
theme/._blast_report_pending.tpl.php


+ 74 - 68
theme/blast_report.tpl.php

@@ -27,6 +27,9 @@
   });
 </script>
 
+<p>The following table summarizes the results of your BLAST. To see additional information
+about each hit including the alignment, click on that row in the table to expand it.</p>
+
 <?php
 
 // Load the XML file
@@ -37,76 +40,79 @@ $xml = simplexml_load_file($xml_filename);
  * @see theme_table() for additional documentation
  */
 
-// Specify the header of the table
-$header = array(
-  'number' =>  array('data' => '#', 'class' => array('number')),
-  'query' =>  array('data' => 'Query Name', 'class' => array('query')),
-  'hit' =>  array('data' => 'Hit Name', 'class' => array('hit')),
-  'evalue' =>  array('data' => 'E-Value', 'class' => array('evalue')),
-  'arrow-col' =>  array('data' => '', 'class' => array('arrow-col'))
-);
-
-$rows = array();
-$count = 0;
-
-// Parse the BLAST XML to generate the rows of the table
-// where each hit results in two rows in the table: 1) A summary of the query/hit and
-// significance and 2) additional information including the alignment
-foreach($xml->{'BlastOutput_iterations'}->children() as $iteration) {
-  foreach($iteration->{'Iteration_hits'}->children() as $hit) {
-    if (is_object($hit)) {
-      $count +=1;
-
-      $zebra_class = ($count % 2 == 0) ? 'even' : 'odd';
-
-      // SUMMARY ROW
-      $hit_name = $hit->Hit_def;
-      if (preg_match('/(\w+)/', $hit_name, $matches)) {
-        $hit_name = $matches[1];
-      }
-      $score = $hit->{'Hit_hsps'}->{'Hsp'}->{'Hsp_score'};
-      $evalue = $hit->{'Hit_hsps'}->{'Hsp'}->{'Hsp_evalue'};
-      $query_name = $iteration->{'Iteration_query-def'};
-
-      $row = array(
-        'data' => array(
-          'number' => array('data' => $count, 'class' => array('number')),
-          'query' => array('data' => $query_name, 'class' => array('query')),
-          'hit' => array('data' => $hit_name, 'class' => array('hit')),
-          'evalue' => array('data' => $evalue, 'class' => array('evalue')),
-          'arrow-col' => array('data' => '<div class="arrow"></div>', 'class' => array('arrow-col'))
-        ),
-        'class' => array('result-summary')
-      );
-      $rows[] = $row;
-
-      // ALIGNMENT ROW (collapsed by default)
-      // Process HSPs
-      $HSPs = array();
-      foreach ($hit->{'Hit_hsps'}->children() as $hsp_xml) {
-        $HSPs[] = (array) $hsp_xml;
-      }
-
-      $row = array(
-        'data' => array(
-          'number' => '',
-          'query' => array(
-            'data' => theme('blast_report_alignment_row', array('HSPs' => $HSPs)),
-            'colspan' => 4,
-          )
-        ),
-        'class' => array('alignment-row', $zebra_class),
-        'no_striping' => TRUE
-      );
-      $rows[] = $row;
+if ($xml) {
+  // Specify the header of the table
+  $header = array(
+    'number' =>  array('data' => '#', 'class' => array('number')),
+    'query' =>  array('data' => 'Query Name', 'class' => array('query')),
+    'hit' =>  array('data' => 'Hit Name', 'class' => array('hit')),
+    'evalue' =>  array('data' => 'E-Value', 'class' => array('evalue')),
+    'arrow-col' =>  array('data' => '', 'class' => array('arrow-col'))
+  );
+
+  $rows = array();
+  $count = 0;
+
+  // Parse the BLAST XML to generate the rows of the table
+  // where each hit results in two rows in the table: 1) A summary of the query/hit and
+  // significance and 2) additional information including the alignment
+  foreach($xml->{'BlastOutput_iterations'}->children() as $iteration) {
+    foreach($iteration->{'Iteration_hits'}->children() as $hit) {
+      if (is_object($hit)) {
+        $count +=1;
+
+        $zebra_class = ($count % 2 == 0) ? 'even' : 'odd';
+
+        // SUMMARY ROW
+        $hit_name = $hit->Hit_id;
+        $score = $hit->{'Hit_hsps'}->{'Hsp'}->{'Hsp_score'};
+        $evalue = $hit->{'Hit_hsps'}->{'Hsp'}->{'Hsp_evalue'};
+        $query_name = $iteration->{'Iteration_query-def'};
+
+        $row = array(
+          'data' => array(
+            'number' => array('data' => $count, 'class' => array('number')),
+            'query' => array('data' => $query_name, 'class' => array('query')),
+            'hit' => array('data' => $hit_name, 'class' => array('hit')),
+            'evalue' => array('data' => $evalue, 'class' => array('evalue')),
+            'arrow-col' => array('data' => '<div class="arrow"></div>', 'class' => array('arrow-col'))
+          ),
+          'class' => array('result-summary')
+        );
+        $rows[] = $row;
+
+        // ALIGNMENT ROW (collapsed by default)
+        // Process HSPs
+        $HSPs = array();
+        foreach ($hit->{'Hit_hsps'}->children() as $hsp_xml) {
+          $HSPs[] = (array) $hsp_xml;
+        }
+
+        $row = array(
+          'data' => array(
+            'number' => '',
+            'query' => array(
+              'data' => theme('blast_report_alignment_row', array('HSPs' => $HSPs)),
+              'colspan' => 4,
+            )
+          ),
+          'class' => array('alignment-row', $zebra_class),
+          'no_striping' => TRUE
+        );
+        $rows[] = $row;
 
+      }
     }
   }
-}
 
-print theme('table', array(
-    'header' => $header,
-    'rows' => $rows,
-    'attributes' => array('id' => 'blast_report'),
-  ));
+  print theme('table', array(
+      'header' => $header,
+      'rows' => $rows,
+      'attributes' => array('id' => 'blast_report'),
+    ));
+}
+else {
+  drupal_set_title('BLAST: Error Encountered');
+  print '<p>We encountered an error and are unable to load your BLAST results.</p>';
+}
 ?>

+ 47 - 0
theme/blast_report_pending.tpl.php

@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * Template to keep the user updated on the progress of their BLAST
+ *
+ * Available Variables:
+ *  - $status_code: a numerical code describing the status of the job. See the table
+ *    below for possible values.
+ *  - $status: a string describing the status of the job. See the table below for
+ *    possible values
+ *
+ *    CODE          STATUS                DESCRIPTION
+ *     0             Pending               The tripal job has been created but has not yet been launched.
+ *     1             Running               The Tripal job is currently running.
+ *    999            Cancelled             The Tripal job was cancelled by an administrator.
+ */
+?>
+
+<?php
+  // JOB IN QUEUE
+  if ($status_code === 0) {
+    drupal_set_title('BLAST Job in Queue');
+?>
+
+  <p>Your BLAST has been registered and will be started shortly.</p>
+
+<?php
+  }
+  // JOB IN PROGRESS
+  elseif ($status_code === 1) {
+    drupal_set_title('BLAST Job in Progress');
+?>
+
+  <p>Your BLAST job is currently running. The results will be listed here as soon as it completes.</p>
+
+<?php
+  }
+  // JOB CANCELLED
+  elseif ($status_code === 999) {
+    drupal_set_title('BLAST Job Cancelled');
+?>
+
+  <p>Unfortunately your BLAST job has been cancelled by an Administrator.</p>
+
+<?php
+ }
+?>

+ 8 - 9
theme/blast_ui.theme.inc

@@ -16,15 +16,14 @@
  */
 function blast_ui_preprocess_show_blast_report(&$vars) {
 
-   $path = drupal_get_path('module', 'blast_ui');
+  // Add CSS and Javascript files
+  $path = drupal_get_path('module', 'blast_ui');
+  drupal_add_css($path . '/theme/blast_report.css');
+  drupal_add_js('http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js');
 
-   drupal_add_css($path . '/theme/blast_report.css');
-   drupal_add_js('http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js');
-
-
-  $path = current_path();
-  if (preg_match('%blast/report/([\w\.]+)%',$path,$matches)) {
-    $vars['xml_filename'] = 'sites/default/files/' . $matches[1];
-  }
+  // Get the filename of the BLAST results
+  $job = tripal_get_job($vars['job_id']);
+  $job_args = unserialize($job->arguments);
+  $vars['xml_filename'] = 'public://' . $job_args['output_filename'];
 
 }