src/core/net/sf/basedb/util/export/spotdata/AbstractBioAssaySetExporter.java

Code
Comments
Other
Rev Date Author Line
4925 08 May 09 nicklas 1 /**
4925 08 May 09 nicklas 2   $Id$
4925 08 May 09 nicklas 3
4925 08 May 09 nicklas 4   Copyright (C) 2009 Nicklas Nordborg
4925 08 May 09 nicklas 5
4925 08 May 09 nicklas 6   This file is part of BASE - BioArray Software Environment.
4925 08 May 09 nicklas 7   Available at http://base.thep.lu.se/
4925 08 May 09 nicklas 8
4925 08 May 09 nicklas 9   BASE is free software; you can redistribute it and/or
4925 08 May 09 nicklas 10   modify it under the terms of the GNU General Public License
4925 08 May 09 nicklas 11   as published by the Free Software Foundation; either version 3
4925 08 May 09 nicklas 12   of the License, or (at your option) any later version.
4925 08 May 09 nicklas 13
4925 08 May 09 nicklas 14   BASE is distributed in the hope that it will be useful,
4925 08 May 09 nicklas 15   but WITHOUT ANY WARRANTY; without even the implied warranty of
4925 08 May 09 nicklas 16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
4925 08 May 09 nicklas 17   GNU General Public License for more details.
4925 08 May 09 nicklas 18
4925 08 May 09 nicklas 19   You should have received a copy of the GNU General Public License
4925 08 May 09 nicklas 20   along with BASE. If not, see <http://www.gnu.org/licenses/>.
4925 08 May 09 nicklas 21 */
4925 08 May 09 nicklas 22 package net.sf.basedb.util.export.spotdata;
4925 08 May 09 nicklas 23
4925 08 May 09 nicklas 24 import java.sql.SQLException;
4925 08 May 09 nicklas 25 import java.util.ArrayList;
4925 08 May 09 nicklas 26 import java.util.HashMap;
4925 08 May 09 nicklas 27 import java.util.List;
4925 08 May 09 nicklas 28 import java.util.Map;
4925 08 May 09 nicklas 29
4925 08 May 09 nicklas 30 import net.sf.basedb.core.AnnotationType;
4925 08 May 09 nicklas 31 import net.sf.basedb.core.BioAssay;
4925 08 May 09 nicklas 32 import net.sf.basedb.core.BioAssaySet;
4925 08 May 09 nicklas 33 import net.sf.basedb.core.DatabaseException;
4925 08 May 09 nicklas 34 import net.sf.basedb.core.DbControl;
4925 08 May 09 nicklas 35 import net.sf.basedb.core.DynamicPositionQuery;
4925 08 May 09 nicklas 36 import net.sf.basedb.core.DynamicResultIterator;
4925 08 May 09 nicklas 37 import net.sf.basedb.core.DynamicSpotQuery;
4925 08 May 09 nicklas 38 import net.sf.basedb.core.Experiment;
4925 08 May 09 nicklas 39 import net.sf.basedb.core.Include;
4925 08 May 09 nicklas 40 import net.sf.basedb.core.ItemQuery;
4925 08 May 09 nicklas 41 import net.sf.basedb.core.ProgressReporter;
4925 08 May 09 nicklas 42 import net.sf.basedb.core.VirtualColumn;
4925 08 May 09 nicklas 43 import net.sf.basedb.core.VirtualTable;
4925 08 May 09 nicklas 44 import net.sf.basedb.core.query.Dynamic;
4925 08 May 09 nicklas 45 import net.sf.basedb.core.query.Expression;
4925 08 May 09 nicklas 46 import net.sf.basedb.core.query.Expressions;
4925 08 May 09 nicklas 47 import net.sf.basedb.core.query.Hql;
4925 08 May 09 nicklas 48 import net.sf.basedb.core.query.JoinType;
4925 08 May 09 nicklas 49 import net.sf.basedb.core.query.Orders;
4925 08 May 09 nicklas 50 import net.sf.basedb.core.query.Restrictions;
4925 08 May 09 nicklas 51 import net.sf.basedb.core.query.Selects;
4925 08 May 09 nicklas 52 import net.sf.basedb.core.query.SqlResult;
5405 10 Sep 10 nicklas 53 import net.sf.basedb.core.signal.ThreadSignalHandler;
5130 13 Oct 09 nicklas 54 import net.sf.basedb.core.snapshot.SnapshotManager;
4925 08 May 09 nicklas 55 import net.sf.basedb.util.formatter.Formatter;
4925 08 May 09 nicklas 56 import net.sf.basedb.util.formatter.ToStringFormatter;
4925 08 May 09 nicklas 57
4925 08 May 09 nicklas 58 /**
4925 08 May 09 nicklas 59   An abstract superclass for all exporters that exports spot data from
4925 08 May 09 nicklas 60   a bioassay set. The purpose of this class is to collect some minimal
4925 08 May 09 nicklas 61   configurations options (such as the source bioassay set and a DbControl)
4925 08 May 09 nicklas 62   and to outline a generic export procedure. This class also has a lot of 
4925 08 May 09 nicklas 63   support functionality for subclass that wants to specify exactly which 
4925 08 May 09 nicklas 64   information that should be exported (spot data, reporter annotations, 
4925 08 May 09 nicklas 65   assay annotations, etc.), query generation, progress reporting, thread
4925 08 May 09 nicklas 66   interruption controls, etc.
4925 08 May 09 nicklas 67   <p>
4925 08 May 09 nicklas 68   
4925 08 May 09 nicklas 69   The exporter has been designed to export three types of information
4925 08 May 09 nicklas 70   associated with a bioassay set.
4925 08 May 09 nicklas 71   <ul>
4925 08 May 09 nicklas 72   <li>Spot data: intensity values, extra values, raw data values
4925 08 May 09 nicklas 73   <li>Reporter data: reporter annotations, position extra values
4925 08 May 09 nicklas 74   <li>Bioassay data: annotations
4925 08 May 09 nicklas 75   </ul>
4925 08 May 09 nicklas 76   
4925 08 May 09 nicklas 77   <p>
4925 08 May 09 nicklas 78   Subclass implementors can override {@link #doExport()} 
4925 08 May 09 nicklas 79   if they wish. A better option is to override the other methods 
4925 08 May 09 nicklas 80   that are called from this method in this order:
4925 08 May 09 nicklas 81   <ol>
4925 08 May 09 nicklas 82     <li>{@link #beginExport()}: One time only
4925 08 May 09 nicklas 83     <li>{@link #exportGlobalHeader()}: One time only. If false is
4925 08 May 09 nicklas 84       returned, the export skips to the last step
4925 08 May 09 nicklas 85     <li>{@link #exportSectionHeader()}: One or more times
4925 08 May 09 nicklas 86     <li>{@link #exportSectionData()}: One or more times
4925 08 May 09 nicklas 87     <li>{@link #exportSectionFooter()}: One or more times. If true is 
4925 08 May 09 nicklas 88       returned, the steps 3-5 are repeated.
4925 08 May 09 nicklas 89     <li>{@link #endExport(RuntimeException)}: Always called last. If
4925 08 May 09 nicklas 90       the parameter is not null an error has occurred
4925 08 May 09 nicklas 91   </ol>
4925 08 May 09 nicklas 92
4925 08 May 09 nicklas 93
4925 08 May 09 nicklas 94   @author Nicklas
4925 08 May 09 nicklas 95   @version 2.12
4925 08 May 09 nicklas 96   @base.modified $Date$
4925 08 May 09 nicklas 97 */
4925 08 May 09 nicklas 98 public abstract class AbstractBioAssaySetExporter
4925 08 May 09 nicklas 99 {
4925 08 May 09 nicklas 100   
4925 08 May 09 nicklas 101   private BioAssaySet source;
4925 08 May 09 nicklas 102   private DbControl dc;
5130 13 Oct 09 nicklas 103   private SnapshotManager snapshotManager;
4925 08 May 09 nicklas 104   private ProgressReporter progress;
4925 08 May 09 nicklas 105   
4925 08 May 09 nicklas 106   private List<AssayField> assayFields;
4925 08 May 09 nicklas 107   private List<DynamicField> reporterFields;
4925 08 May 09 nicklas 108   private List<DynamicField> spotFields;
4925 08 May 09 nicklas 109   private boolean averageOnReporter;
4925 08 May 09 nicklas 110
4925 08 May 09 nicklas 111   private List<BioAssay> bioAssays;
4925 08 May 09 nicklas 112   private Map<Integer, Object[]> reporterCache;
4926 11 May 09 nicklas 113   private Map<Short, Integer> assayIndexMap;
4925 08 May 09 nicklas 114   
4925 08 May 09 nicklas 115   protected AbstractBioAssaySetExporter()
4925 08 May 09 nicklas 116   {
4925 08 May 09 nicklas 117     this.spotFields = new ArrayList<DynamicField>();
4925 08 May 09 nicklas 118     this.reporterFields = new ArrayList<DynamicField>();
4925 08 May 09 nicklas 119     this.assayFields = new ArrayList<AssayField>();
4925 08 May 09 nicklas 120   }
4925 08 May 09 nicklas 121   
4925 08 May 09 nicklas 122   /*
4925 08 May 09 nicklas 123     public Configuration properties
4925 08 May 09 nicklas 124     -------------------------------
4925 08 May 09 nicklas 125   */
4925 08 May 09 nicklas 126   /**
4925 08 May 09 nicklas 127     Set's the DbControl that should be used to get data from the database.
4925 08 May 09 nicklas 128   */
4925 08 May 09 nicklas 129   public void setDbControl(DbControl dc)
4925 08 May 09 nicklas 130   {
4925 08 May 09 nicklas 131     this.dc = dc;
4925 08 May 09 nicklas 132   }
4925 08 May 09 nicklas 133   
4925 08 May 09 nicklas 134   /**
4925 08 May 09 nicklas 135     Get the current DbControl.
4925 08 May 09 nicklas 136   */
4925 08 May 09 nicklas 137   public DbControl getDbControl()
4925 08 May 09 nicklas 138   {
4925 08 May 09 nicklas 139     return dc;
4925 08 May 09 nicklas 140   }
4925 08 May 09 nicklas 141   
4925 08 May 09 nicklas 142   /**
5130 13 Oct 09 nicklas 143     Set's the snapshot manager that should be used to load annotation values.
5130 13 Oct 09 nicklas 144     If no snapshot manager is provided an internal, temporary one is used.
5130 13 Oct 09 nicklas 145     @since 2.14
5130 13 Oct 09 nicklas 146   */
5130 13 Oct 09 nicklas 147   public void setSnapshotManager(SnapshotManager snapshotManager)
5130 13 Oct 09 nicklas 148   {
5130 13 Oct 09 nicklas 149     this.snapshotManager = snapshotManager;
5130 13 Oct 09 nicklas 150   }
5130 13 Oct 09 nicklas 151   
5130 13 Oct 09 nicklas 152   /**
5130 13 Oct 09 nicklas 153     Get the current snapshot manager.
5130 13 Oct 09 nicklas 154     @since 2.14
5130 13 Oct 09 nicklas 155   */
5130 13 Oct 09 nicklas 156   public SnapshotManager getSnapshotManager()
5130 13 Oct 09 nicklas 157   {
5130 13 Oct 09 nicklas 158     return snapshotManager;
5130 13 Oct 09 nicklas 159   }
5130 13 Oct 09 nicklas 160   
5130 13 Oct 09 nicklas 161   /**
4925 08 May 09 nicklas 162     Set the progress reporter that is used to report progress. Subclasses
4925 08 May 09 nicklas 163     can call {@link #setProgress(int, String)} to update the current status.
4925 08 May 09 nicklas 164   */
4925 08 May 09 nicklas 165   public void setProgressReporter(ProgressReporter progress)
4925 08 May 09 nicklas 166   {
4925 08 May 09 nicklas 167     this.progress = progress;
4925 08 May 09 nicklas 168   }
4925 08 May 09 nicklas 169   
4925 08 May 09 nicklas 170   /**
4925 08 May 09 nicklas 171     Get the progress reporter
4925 08 May 09 nicklas 172   */
4925 08 May 09 nicklas 173   public ProgressReporter getProgressReporter()
4925 08 May 09 nicklas 174   {
4925 08 May 09 nicklas 175     return progress;
4925 08 May 09 nicklas 176   }
4925 08 May 09 nicklas 177   
4925 08 May 09 nicklas 178   /**
4925 08 May 09 nicklas 179     Set the bioassay set that is used as a source for the spot data.
4925 08 May 09 nicklas 180   */
4925 08 May 09 nicklas 181   public void setSource(BioAssaySet source)
4925 08 May 09 nicklas 182   {
4925 08 May 09 nicklas 183     this.source = source;
4925 08 May 09 nicklas 184   }
4925 08 May 09 nicklas 185   
4925 08 May 09 nicklas 186   /**
4925 08 May 09 nicklas 187     Get the source bioassay set.
4925 08 May 09 nicklas 188   */
4925 08 May 09 nicklas 189   public BioAssaySet getSource()
4925 08 May 09 nicklas 190   {
4925 08 May 09 nicklas 191     return source;
4925 08 May 09 nicklas 192   }
4925 08 May 09 nicklas 193   // -----------------------------------
4925 08 May 09 nicklas 194   
4925 08 May 09 nicklas 195   /*
4925 08 May 09 nicklas 196     Optional configuration properties
4925 08 May 09 nicklas 197     ----------------------------------
4925 08 May 09 nicklas 198   */
4925 08 May 09 nicklas 199   /**
4925 08 May 09 nicklas 200     Add information about a spot field that should be exported.
4925 08 May 09 nicklas 201     @param field The spot field (null is ignored)
4925 08 May 09 nicklas 202   */
4925 08 May 09 nicklas 203   protected void addSpotField(DynamicField field)
4925 08 May 09 nicklas 204   {
4925 08 May 09 nicklas 205     if (field != null) spotFields.add(field);
4925 08 May 09 nicklas 206   }
4925 08 May 09 nicklas 207   
4925 08 May 09 nicklas 208   /**
4925 08 May 09 nicklas 209     Get the list of registered spot fields, in the order they were 
4925 08 May 09 nicklas 210     registered.
4925 08 May 09 nicklas 211   */
4925 08 May 09 nicklas 212   protected List<DynamicField> getSpotFields()
4925 08 May 09 nicklas 213   {
4925 08 May 09 nicklas 214     return spotFields;
4925 08 May 09 nicklas 215   }
4925 08 May 09 nicklas 216   
4925 08 May 09 nicklas 217   /**
4925 08 May 09 nicklas 218     Add information about a reporter field that should be exported.
4925 08 May 09 nicklas 219     @param field The reporter field (null is ignored)
4925 08 May 09 nicklas 220   */
4925 08 May 09 nicklas 221   protected void addReporterField(DynamicField field)
4925 08 May 09 nicklas 222   {
4925 08 May 09 nicklas 223     if (field != null) reporterFields.add(field);
4925 08 May 09 nicklas 224   }
4925 08 May 09 nicklas 225   
4925 08 May 09 nicklas 226   /**
4925 08 May 09 nicklas 227     Get the list of registered reporter fields, in the order they were 
4925 08 May 09 nicklas 228     registered.
4925 08 May 09 nicklas 229   */
4925 08 May 09 nicklas 230   protected List<DynamicField> getReporterFields()
4925 08 May 09 nicklas 231   {
4925 08 May 09 nicklas 232     return reporterFields;
4925 08 May 09 nicklas 233   }
4925 08 May 09 nicklas 234   
4925 08 May 09 nicklas 235   /**
4925 08 May 09 nicklas 236     Add information about an assay field that should be exported.
4925 08 May 09 nicklas 237     @param field The assay field (null is ignored)
4925 08 May 09 nicklas 238   */
4925 08 May 09 nicklas 239   protected void addAssayField(AssayField field)
4925 08 May 09 nicklas 240   {
4925 08 May 09 nicklas 241     if (field != null) assayFields.add(field);
4925 08 May 09 nicklas 242   }
4925 08 May 09 nicklas 243   
4925 08 May 09 nicklas 244   /**
4925 08 May 09 nicklas 245     Get the list of registered assay fields, in the order they were 
4925 08 May 09 nicklas 246     registered.
4925 08 May 09 nicklas 247   */
4925 08 May 09 nicklas 248   protected List<AssayField> getAssayFields()
4925 08 May 09 nicklas 249   {
4925 08 May 09 nicklas 250     return assayFields;
4925 08 May 09 nicklas 251   }
4925 08 May 09 nicklas 252
4925 08 May 09 nicklas 253   /**
4925 08 May 09 nicklas 254     Specify if the exported data should be averaged on reporter
4925 08 May 09 nicklas 255     or not.
4925 08 May 09 nicklas 256   */
4925 08 May 09 nicklas 257   protected void setAverageOnReporter(boolean averageOnReporter)
4925 08 May 09 nicklas 258   {
4925 08 May 09 nicklas 259     this.averageOnReporter = averageOnReporter;
4925 08 May 09 nicklas 260   }
4925 08 May 09 nicklas 261
4925 08 May 09 nicklas 262   protected boolean getAverageOnReporter()
4925 08 May 09 nicklas 263   {
4925 08 May 09 nicklas 264     return averageOnReporter;
4925 08 May 09 nicklas 265   }
4925 08 May 09 nicklas 266   // ---------------------------------------------
4925 08 May 09 nicklas 267   
4925 08 May 09 nicklas 268   /**
4925 08 May 09 nicklas 269     Run the export. 
4925 08 May 09 nicklas 270   */
4925 08 May 09 nicklas 271   public void doExport()
4925 08 May 09 nicklas 272   {
4925 08 May 09 nicklas 273     RuntimeException e = null;
5199 15 Dec 09 nicklas 274     validate();
4925 08 May 09 nicklas 275     try
4925 08 May 09 nicklas 276     {
4925 08 May 09 nicklas 277       beginExport();
4925 08 May 09 nicklas 278       boolean cntinue = exportGlobalHeader();
4925 08 May 09 nicklas 279       while (cntinue)
4925 08 May 09 nicklas 280       {
4925 08 May 09 nicklas 281         exportSectionHeader();
4925 08 May 09 nicklas 282         exportSectionData();
4925 08 May 09 nicklas 283         cntinue = exportSectionFooter();
4925 08 May 09 nicklas 284       }
4925 08 May 09 nicklas 285       exportGlobalFooter();
4925 08 May 09 nicklas 286     }
4925 08 May 09 nicklas 287     catch (RuntimeException ex)
4925 08 May 09 nicklas 288     {
4925 08 May 09 nicklas 289       e = ex;
4925 08 May 09 nicklas 290       throw e;
4925 08 May 09 nicklas 291     }
4925 08 May 09 nicklas 292     finally
4925 08 May 09 nicklas 293     {
4925 08 May 09 nicklas 294       endExport(e);
4925 08 May 09 nicklas 295     }
4925 08 May 09 nicklas 296   }
4925 08 May 09 nicklas 297
4925 08 May 09 nicklas 298   /*
5199 15 Dec 09 nicklas 299     Export methods that may be implemented by subclasses
5199 15 Dec 09 nicklas 300     ----------------------------------------------------
4925 08 May 09 nicklas 301   */
4925 08 May 09 nicklas 302   /**
5199 15 Dec 09 nicklas 303     Validate that all required options has been set and that other
5199 15 Dec 09 nicklas 304     options have sensible values. Subclasses that override this method
5199 15 Dec 09 nicklas 305     should call super.validate(). The default implementation will at least
5199 15 Dec 09 nicklas 306     validate that a DbControl and a source bioassay set has been set.
5199 15 Dec 09 nicklas 307     @since 2.15
5199 15 Dec 09 nicklas 308   */
5199 15 Dec 09 nicklas 309   protected void validate()
5199 15 Dec 09 nicklas 310   {
5199 15 Dec 09 nicklas 311     if (getDbControl() == null) throw new NullPointerException("getDbControl()");
5199 15 Dec 09 nicklas 312     if (getSource() == null) throw new NullPointerException("getSource()");
5199 15 Dec 09 nicklas 313   }
5199 15 Dec 09 nicklas 314   
5199 15 Dec 09 nicklas 315   /**
4925 08 May 09 nicklas 316     Make preparations for the export. Subclasses that need to initialise
4925 08 May 09 nicklas 317     things should override this method. This is the first method that
4925 08 May 09 nicklas 318     is called from {@link #doExport()} and is only called once. The
4925 08 May 09 nicklas 319     default implementation does nothing.
4925 08 May 09 nicklas 320   */
4925 08 May 09 nicklas 321   protected void beginExport()
4925 08 May 09 nicklas 322   {}
4925 08 May 09 nicklas 323   
4925 08 May 09 nicklas 324   /**
4925 08 May 09 nicklas 325     Export global headers or other data that only appear once in the
4925 08 May 09 nicklas 326     beginning of the exported file. This method is called after {@link 
4925 08 May 09 nicklas 327     #beginExport()} and is only called once. The return value can be used
4925 08 May 09 nicklas 328     to indicate if the export should continue with sections or with global 
4925 08 May 09 nicklas 329     footers. The default implementation does nothing and returns TRUE.
4925 08 May 09 nicklas 330     @return TRUE to continue with a section {@link #exportSectionHeader()}, 
4925 08 May 09 nicklas 331       FALSE to continue with global footers {@link #exportGlobalFooter()}
4925 08 May 09 nicklas 332   */
4925 08 May 09 nicklas 333   protected boolean exportGlobalHeader()
4925 08 May 09 nicklas 334   {
4925 08 May 09 nicklas 335     return true;
4925 08 May 09 nicklas 336   }
4925 08 May 09 nicklas 337   
4925 08 May 09 nicklas 338   /**
4925 08 May 09 nicklas 339     Start a new section in the export by exporting section headers.
4925 08 May 09 nicklas 340     This method is called after {@link #exportGlobalHeader()} and may
4925 08 May 09 nicklas 341     be called multiple times as determined by the return value of
4925 08 May 09 nicklas 342     {@link #exportSectionFooter()}. The default implementation does
4925 08 May 09 nicklas 343     nothing.
4925 08 May 09 nicklas 344   */
4925 08 May 09 nicklas 345   protected void exportSectionHeader()
4925 08 May 09 nicklas 346   {}
4925 08 May 09 nicklas 347   
4925 08 May 09 nicklas 348   /**
4925 08 May 09 nicklas 349     Export section data. This method is called after {@link #exportSectionHeader()}
4925 08 May 09 nicklas 350     and may be called multiple times as determined by the return value of
4925 08 May 09 nicklas 351     {@link #exportSectionFooter()}. The default implementation does
4925 08 May 09 nicklas 352     nothing.
4925 08 May 09 nicklas 353   */
4925 08 May 09 nicklas 354   protected void exportSectionData()
4925 08 May 09 nicklas 355   {}
4925 08 May 09 nicklas 356   
4925 08 May 09 nicklas 357   /**
4925 08 May 09 nicklas 358     End the section by exporting a footer. This method is called after {@link 
4925 08 May 09 nicklas 359     #exportSectionData()}. The return value is used to determine if the export
4925 08 May 09 nicklas 360     should be repeated with a new section or if it should move on to global footers.
4925 08 May 09 nicklas 361     The default implementation returns false.
4925 08 May 09 nicklas 362     @return TRUE to continue with another section {@link #exportSectionHeader()}, 
4925 08 May 09 nicklas 363       FALSE to continue with global footers {@link #exportGlobalFooter()}
4925 08 May 09 nicklas 364    */
4925 08 May 09 nicklas 365   protected boolean exportSectionFooter()
4925 08 May 09 nicklas 366   {
4925 08 May 09 nicklas 367     return false;
4925 08 May 09 nicklas 368   }
4925 08 May 09 nicklas 369
4925 08 May 09 nicklas 370   /**
4925 08 May 09 nicklas 371     Export global footer or other data that only appears once in the
4925 08 May 09 nicklas 372     end of the exported file. This method is called after FALSE has been
4925 08 May 09 nicklas 373     returned from either {@link #exportGlobalHeader()} or {@link 
4925 08 May 09 nicklas 374     #exportSectionFooter()}. The default implementation does nothing.
4925 08 May 09 nicklas 375   */
4925 08 May 09 nicklas 376   protected void exportGlobalFooter()
4925 08 May 09 nicklas 377   {}
4925 08 May 09 nicklas 378   
4925 08 May 09 nicklas 379   /**
4925 08 May 09 nicklas 380     End the export and clean up/close all aquired resources. This method
4925 08 May 09 nicklas 381     is always called last. In the case of an error the exception parameter
4925 08 May 09 nicklas 382     has a non-null value. The default implementation clears up values that 
4925 08 May 09 nicklas 383     has been cached so it is recommended that subclasses always call this 
4925 08 May 09 nicklas 384     method as part of their own cleanup. If the subclass implementation 
4925 08 May 09 nicklas 385     wants to throw a different exception it is recommended that the given 
4925 08 May 09 nicklas 386     exception is chained to allow developers to debug problems.
4925 08 May 09 nicklas 387   */
4925 08 May 09 nicklas 388   protected void endExport(RuntimeException e)
4925 08 May 09 nicklas 389   {
4925 08 May 09 nicklas 390     if (bioAssays != null) bioAssays.clear();
4925 08 May 09 nicklas 391     bioAssays = null;
4925 08 May 09 nicklas 392     if (reporterCache != null) reporterCache.clear();
4925 08 May 09 nicklas 393     reporterCache = null;
4925 08 May 09 nicklas 394   }
4925 08 May 09 nicklas 395   // -------------------------------------------
4925 08 May 09 nicklas 396   
4925 08 May 09 nicklas 397   /*
4925 08 May 09 nicklas 398     Helper methods
4925 08 May 09 nicklas 399     --------------
4925 08 May 09 nicklas 400   */
4925 08 May 09 nicklas 401   /**
4925 08 May 09 nicklas 402     Get a list with all bioassays in the source bioassay set. The list
4925 08 May 09 nicklas 403     is sorted by assay name and id. The list is cached internally and
4925 08 May 09 nicklas 404     if this method is called several times, the same list is returned.
4925 08 May 09 nicklas 405   */
4925 08 May 09 nicklas 406   protected List<BioAssay> getBioAssays()
4925 08 May 09 nicklas 407   {
4925 08 May 09 nicklas 408     if (bioAssays == null)
4925 08 May 09 nicklas 409     {
4925 08 May 09 nicklas 410       ItemQuery<BioAssay> query = getSource().getBioAssays();
4925 08 May 09 nicklas 411       query.order(Orders.asc(Hql.property("name")));
4925 08 May 09 nicklas 412       query.order(Orders.asc(Hql.property("id")));
4925 08 May 09 nicklas 413       bioAssays = new ArrayList<BioAssay>(query.list(getDbControl()));
4925 08 May 09 nicklas 414     }
4925 08 May 09 nicklas 415     return bioAssays;
4925 08 May 09 nicklas 416   }
4925 08 May 09 nicklas 417   
4925 08 May 09 nicklas 418   /**
4925 08 May 09 nicklas 419     Adds all experimental factors from the experiment that the
4925 08 May 09 nicklas 420     source bioassay set belongs to as assay fields. The experimental
4925 08 May 09 nicklas 421     factors are ordered by name.
4925 08 May 09 nicklas 422   */
4925 08 May 09 nicklas 423   protected void addExperimentalFactorsAsAssayFields()
4925 08 May 09 nicklas 424   {
4925 08 May 09 nicklas 425     // Get configuration options
4925 08 May 09 nicklas 426     DbControl dc = getDbControl();
4925 08 May 09 nicklas 427     BioAssaySet source = getSource();
4925 08 May 09 nicklas 428     Experiment exp = BioAssaySet.getById(dc, source.getId()).getExperiment();
4925 08 May 09 nicklas 429
5130 13 Oct 09 nicklas 430     SnapshotManager sm = getSnapshotManager();
5130 13 Oct 09 nicklas 431     if (sm == null) sm = new SnapshotManager();
5130 13 Oct 09 nicklas 432     
4925 08 May 09 nicklas 433     // Load the experimental factors and register them
4925 08 May 09 nicklas 434     ItemQuery<AnnotationType> query = exp.getExperimentalFactors();
4925 08 May 09 nicklas 435     query.include(Include.MINE, Include.SHARED, Include.IN_PROJECT, Include.OTHERS);
4925 08 May 09 nicklas 436     query.order(Orders.asc(Hql.property("name")));
4925 08 May 09 nicklas 437     for (AnnotationType at : query.list(dc))
4925 08 May 09 nicklas 438     {
4925 08 May 09 nicklas 439       AnnotationAssayField af = new AnnotationAssayField();
4925 08 May 09 nicklas 440       af.setAnnotationType(at);
5130 13 Oct 09 nicklas 441       af.setSnapshotManager(sm);
4925 08 May 09 nicklas 442       addAssayField(af);
4925 08 May 09 nicklas 443     }
4925 08 May 09 nicklas 444   }
4925 08 May 09 nicklas 445   
4925 08 May 09 nicklas 446   /**
4925 08 May 09 nicklas 447     Get a configured query for spot data that returns all
4925 08 May 09 nicklas 448     fields that has been registered with {@link #addSpotField(DynamicField)}.
4925 08 May 09 nicklas 449     The fields are selected in the order they were registered. 
4925 08 May 09 nicklas 450     This method also adds the position and column of the spot as the
4925 08 May 09 nicklas 451     last two fields. Note! If the {@link #setAverageOnReporter(boolean)}
4925 08 May 09 nicklas 452     option has been enabled there is no unique position so the query
4925 08 May 09 nicklas 453     returns the reporter id instead of the position in this case.
4925 08 May 09 nicklas 454     
4925 08 May 09 nicklas 455     @param bioAssayRestriction If TRUE a restriction is added to the 
4925 08 May 09 nicklas 456       query to make it return data for a single bioassay only.
4925 08 May 09 nicklas 457       Use <code>query.setParameter("bioAssayColumn", (int)bioAssay.getDataCubeColumnNo(), Type.INT)</code> 
4925 08 May 09 nicklas 458       to set the value for the restriction
4925 08 May 09 nicklas 459     @return A prepared query
4925 08 May 09 nicklas 460   */
4925 08 May 09 nicklas 461   protected DynamicSpotQuery getSpotQuery(boolean bioAssayRestriction)
4925 08 May 09 nicklas 462   {
4925 08 May 09 nicklas 463     // Get configuration options
4925 08 May 09 nicklas 464     DbControl dc = getDbControl();
4925 08 May 09 nicklas 465     BioAssaySet source = getSource();
4925 08 May 09 nicklas 466     boolean forAverage = getAverageOnReporter();
4925 08 May 09 nicklas 467     
4925 08 May 09 nicklas 468     // Create and prepare query
4925 08 May 09 nicklas 469     DynamicSpotQuery query = source.getSpotData();
4925 08 May 09 nicklas 470     if (forAverage) query.joinReporters(JoinType.INNER);
4925 08 May 09 nicklas 471     
4925 08 May 09 nicklas 472     // Add spot fields
4925 08 May 09 nicklas 473     int index = 0;
4925 08 May 09 nicklas 474     Expression emptyExpression = Expressions.string("");
4925 08 May 09 nicklas 475     for (DynamicField f : getSpotFields())
4925 08 May 09 nicklas 476     {
5319 20 Apr 10 nicklas 477       Expression e = f.getExpression(dc, query, source, forAverage);
4925 08 May 09 nicklas 478       if (e == null)  e = emptyExpression;
4925 08 May 09 nicklas 479       query.select(Selects.expression(e, "f" + index));
4925 08 May 09 nicklas 480       index++;
4925 08 May 09 nicklas 481     }
4925 08 May 09 nicklas 482     
4925 08 May 09 nicklas 483     // Add position and column fields
4925 08 May 09 nicklas 484     Expression posExpression = null;
4925 08 May 09 nicklas 485     Expression colExpression = Dynamic.column(VirtualColumn.COLUMN);
4925 08 May 09 nicklas 486     if (forAverage)
4925 08 May 09 nicklas 487     {
4925 08 May 09 nicklas 488       posExpression = Dynamic.column(VirtualTable.POSITION, VirtualColumn.REPORTER_ID);
4925 08 May 09 nicklas 489       query.group(colExpression);
4925 08 May 09 nicklas 490       query.group(posExpression);
4925 08 May 09 nicklas 491     }
4925 08 May 09 nicklas 492     else
4925 08 May 09 nicklas 493     {
4925 08 May 09 nicklas 494       posExpression = Dynamic.column(VirtualColumn.POSITION);
4925 08 May 09 nicklas 495     }
4925 08 May 09 nicklas 496     query.select(Selects.expression(posExpression, "pos"));
4925 08 May 09 nicklas 497     query.select(Selects.expression(colExpression, "col"));
4925 08 May 09 nicklas 498     query.order(Orders.asc(posExpression));
4925 08 May 09 nicklas 499     query.order(Orders.asc(colExpression));
4925 08 May 09 nicklas 500     
4925 08 May 09 nicklas 501     // Optional restriction
4925 08 May 09 nicklas 502     if (bioAssayRestriction)
4925 08 May 09 nicklas 503     {
4925 08 May 09 nicklas 504       query.restrict(Restrictions.eq(colExpression, Expressions.parameter("bioAssayColumn")));
4925 08 May 09 nicklas 505     }
4925 08 May 09 nicklas 506     return query;
4925 08 May 09 nicklas 507   }
4925 08 May 09 nicklas 508   
4925 08 May 09 nicklas 509   /**
4925 08 May 09 nicklas 510     Get a configured query for position data that returns all
4925 08 May 09 nicklas 511     fields that has been registered with {@link #addReporterField(DynamicField)}.
4925 08 May 09 nicklas 512     The fields are selected in the order they were registered. This method also 
4925 08 May 09 nicklas 513     adds the position of the spot as the last field. 
4925 08 May 09 nicklas 514     <p>
4925 08 May 09 nicklas 515     Note 1! If the {@link #setAverageOnReporter(boolean)}
4925 08 May 09 nicklas 516     option has been enabled there is no unique position so the query
4925 08 May 09 nicklas 517     returns the reporter id instead of the position in this case.
4925 08 May 09 nicklas 518     <p>
4925 08 May 09 nicklas 519     Note 2!  If the {@link #setAverageOnReporter(boolean)}
4925 08 May 09 nicklas 520     option has been enabled the query may return the same reporter
4925 08 May 09 nicklas 521     information multiple times.
4925 08 May 09 nicklas 522     <p>
4925 08 May 09 nicklas 523     Note 3! A subclass may override this method, but it should at least still
4925 08 May 09 nicklas 524     select the same number of fields as is expected from the default 
4925 08 May 09 nicklas 525     implementation.
4925 08 May 09 nicklas 526     @return A prepared query
4925 08 May 09 nicklas 527   */
4925 08 May 09 nicklas 528   protected DynamicPositionQuery getReporterQuery()
4925 08 May 09 nicklas 529   {
4925 08 May 09 nicklas 530     // Get configuration options
4925 08 May 09 nicklas 531     DbControl dc = getDbControl();
4925 08 May 09 nicklas 532     BioAssaySet source = getSource();
4925 08 May 09 nicklas 533     boolean forAverage = getAverageOnReporter();
4925 08 May 09 nicklas 534
4925 08 May 09 nicklas 535     // Create and prepare query
4925 08 May 09 nicklas 536     DynamicPositionQuery query = source.getPositionData();
4926 11 May 09 nicklas 537     query.joinReporters(JoinType.LEFT);
4925 08 May 09 nicklas 538     
4925 08 May 09 nicklas 539     // Add reporter fields
4925 08 May 09 nicklas 540     int index = 0;
4925 08 May 09 nicklas 541     Expression emptyExpression = Expressions.string("");
4925 08 May 09 nicklas 542     for (DynamicField f : getReporterFields())
4925 08 May 09 nicklas 543     {
5319 20 Apr 10 nicklas 544       Expression e = f.getExpression(dc, query, source, false);
4925 08 May 09 nicklas 545       if (e == null)  e = emptyExpression;
4925 08 May 09 nicklas 546       query.select(Selects.expression(e, "f" + index));
4925 08 May 09 nicklas 547       index++;
4925 08 May 09 nicklas 548     }
4925 08 May 09 nicklas 549     
4925 08 May 09 nicklas 550     // Add position field
4925 08 May 09 nicklas 551     Expression posExpression = null;
4925 08 May 09 nicklas 552     if (forAverage)
4925 08 May 09 nicklas 553     {
4925 08 May 09 nicklas 554       posExpression = Dynamic.column(VirtualTable.POSITION, VirtualColumn.REPORTER_ID);
4925 08 May 09 nicklas 555     }
4925 08 May 09 nicklas 556     else
4925 08 May 09 nicklas 557     {
4925 08 May 09 nicklas 558       posExpression = Dynamic.column(VirtualColumn.POSITION);
4925 08 May 09 nicklas 559     }
4925 08 May 09 nicklas 560     query.select(Selects.expression(posExpression, "pos"));
7390 07 Jun 17 nicklas 561     query.order(Orders.asc(posExpression));
4925 08 May 09 nicklas 562     return query;
4925 08 May 09 nicklas 563   }
4925 08 May 09 nicklas 564
4925 08 May 09 nicklas 565   /**
4925 08 May 09 nicklas 566     Loads and caches reporter data in an internal structure
4925 08 May 09 nicklas 567     for quick access. This method will execute the query returned
4925 08 May 09 nicklas 568     by {@link #getReporterQuery()} and read all data into a memory
4925 08 May 09 nicklas 569     structure. The data is indexed by position number or by reporter
4925 08 May 09 nicklas 570     id (if average on reporters is enabled).
4925 08 May 09 nicklas 571   */
6875 20 Apr 15 nicklas 572   @SuppressWarnings({ "unchecked", "rawtypes" })
4925 08 May 09 nicklas 573   protected int cacheReporterData()
4925 08 May 09 nicklas 574   {
4925 08 May 09 nicklas 575     // Get configuration options
4925 08 May 09 nicklas 576     DbControl dc = getDbControl();
4925 08 May 09 nicklas 577     BioAssaySet source = getSource();
4925 08 May 09 nicklas 578     List<DynamicField> reporterFields = getReporterFields();
4925 08 May 09 nicklas 579     int numReporterFields = reporterFields.size();
4925 08 May 09 nicklas 580     int posIndex = numReporterFields + 1;
4925 08 May 09 nicklas 581     Formatter[] formatters = new Formatter[numReporterFields];
4925 08 May 09 nicklas 582     for (int i = 0; i < numReporterFields; ++i)
4925 08 May 09 nicklas 583     {
4925 08 May 09 nicklas 584       Formatter f = reporterFields.get(i).getFormatter();
4925 08 May 09 nicklas 585       if (f == null) f = new ToStringFormatter();
4925 08 May 09 nicklas 586       formatters[i] = f;
4925 08 May 09 nicklas 587     }
4925 08 May 09 nicklas 588     
4925 08 May 09 nicklas 589     // Get reporter query
4925 08 May 09 nicklas 590     DynamicPositionQuery query = getReporterQuery();
4925 08 May 09 nicklas 591     
4925 08 May 09 nicklas 592     reporterCache = new HashMap<Integer, Object[]>(source.getNumSpots());
4925 08 May 09 nicklas 593     DynamicResultIterator it = query.iterate(dc);
4925 08 May 09 nicklas 594     try
4925 08 May 09 nicklas 595     {
4925 08 May 09 nicklas 596       while (it.hasNext())
4925 08 May 09 nicklas 597       {
5590 16 Mar 11 nicklas 598         ThreadSignalHandler.checkInterrupted();
4925 08 May 09 nicklas 599         SqlResult result = it.next();
4925 08 May 09 nicklas 600         int position = result.getInt(posIndex);
4925 08 May 09 nicklas 601         if (!reporterCache.containsKey(position))
4925 08 May 09 nicklas 602         {
4925 08 May 09 nicklas 603           Object[] data = new Object[numReporterFields];
4925 08 May 09 nicklas 604           int index = 0;
4925 08 May 09 nicklas 605           for (int i = 1; i <= numReporterFields; ++i)
4925 08 May 09 nicklas 606           {
4925 08 May 09 nicklas 607             data[index] = formatters[index].format(result.getObject(i));
4925 08 May 09 nicklas 608             ++index;
4925 08 May 09 nicklas 609           }
4925 08 May 09 nicklas 610           reporterCache.put(position, data);
4925 08 May 09 nicklas 611         }
4925 08 May 09 nicklas 612       }
4925 08 May 09 nicklas 613     }
4925 08 May 09 nicklas 614     catch (SQLException e)
4925 08 May 09 nicklas 615     {
4925 08 May 09 nicklas 616       throw new DatabaseException(e);
4925 08 May 09 nicklas 617     }
4925 08 May 09 nicklas 618     finally
4925 08 May 09 nicklas 619     {
4925 08 May 09 nicklas 620       if (it != null) it.close();
4925 08 May 09 nicklas 621     }
4925 08 May 09 nicklas 622     return reporterCache.size();
4925 08 May 09 nicklas 623   }
4925 08 May 09 nicklas 624   
4925 08 May 09 nicklas 625   /**
4926 11 May 09 nicklas 626     Prepare a cache that maps the bioassay column ({@link BioAssay#getDataCubeColumnNo()})
4926 11 May 09 nicklas 627     value to an index in the 'data' array this is generated. The map is generated as
4926 11 May 09 nicklas 628     follows:
4926 11 May 09 nicklas 629     <ul>
4926 11 May 09 nicklas 630     <li>first assay -&gt; firstIndex
4926 11 May 09 nicklas 631     <li>second assay -&gt; firstIndex + columnsPerAssay
4926 11 May 09 nicklas 632     <li>third assay -&gt; firtsIndex + 2 * columnsPerAssay
4926 11 May 09 nicklas 633     <li>... and so on
4926 11 May 09 nicklas 634     </ul>
4926 11 May 09 nicklas 635     <p>
4926 11 May 09 nicklas 636     Use {@link #getAssayIndex(short)} to get the indexed values.
4926 11 May 09 nicklas 637     
4926 11 May 09 nicklas 638     @param assays The assays that should be mapped
4926 11 May 09 nicklas 639     @param firstIndex The index of the first assay
4926 11 May 09 nicklas 640     @param columnsPerAssay Number of columns for each assay
4926 11 May 09 nicklas 641   */
4926 11 May 09 nicklas 642   protected void prepareAssayIndexMap(List<BioAssay> assays, int firstIndex, int columnsPerAssay)
4926 11 May 09 nicklas 643   {
4926 11 May 09 nicklas 644     if (assayIndexMap == null) assayIndexMap = new HashMap<Short, Integer>(assays.size());
4926 11 May 09 nicklas 645     int index = firstIndex;
4926 11 May 09 nicklas 646     for (BioAssay ba : assays)
4926 11 May 09 nicklas 647     {
4926 11 May 09 nicklas 648       assayIndexMap.put(ba.getDataCubeColumnNo(), index);
4926 11 May 09 nicklas 649       index += columnsPerAssay;
4926 11 May 09 nicklas 650     }
4926 11 May 09 nicklas 651   }
4926 11 May 09 nicklas 652   
4926 11 May 09 nicklas 653   /**
4926 11 May 09 nicklas 654     Get the cached index value for an assay when you know the assay's column
4926 11 May 09 nicklas 655     number.
4926 11 May 09 nicklas 656     @param column The column number ({@link BioAssay#getDataCubeColumnNo()})
4926 11 May 09 nicklas 657     @return The index of the first position in the 'data' array that
4926 11 May 09 nicklas 658       data for the assay should be written to.
4926 11 May 09 nicklas 659     @see #prepareAssayIndexMap(List, int, int)
4926 11 May 09 nicklas 660   */
4926 11 May 09 nicklas 661   protected int getAssayIndex(short column)
4926 11 May 09 nicklas 662   {
4926 11 May 09 nicklas 663     return assayIndexMap.get(column);
4926 11 May 09 nicklas 664   }
4926 11 May 09 nicklas 665   
4926 11 May 09 nicklas 666   /**
4925 08 May 09 nicklas 667     Copy cached reporter data into a data array. 
4925 08 May 09 nicklas 668     @param key The cache key (=position or reporter id)
4925 08 May 09 nicklas 669     @param data The data array to copy to
4925 08 May 09 nicklas 670     @param startIndex The start index in the data array for the
4925 08 May 09 nicklas 671       first reporter field
4925 08 May 09 nicklas 672   */
4925 08 May 09 nicklas 673   protected void copyReporterFields(int key, Object[] data, int startIndex)
4925 08 May 09 nicklas 674   {
4925 08 May 09 nicklas 675     Object[] reporterData = reporterCache.get(key);
4925 08 May 09 nicklas 676     if (reporterData != null)
4925 08 May 09 nicklas 677     {
4925 08 May 09 nicklas 678       System.arraycopy(reporterData, 0, data, startIndex, reporterData.length);
4925 08 May 09 nicklas 679     }
4925 08 May 09 nicklas 680   }
4925 08 May 09 nicklas 681   
4925 08 May 09 nicklas 682   /**
4925 08 May 09 nicklas 683     Update the progress of the export.
4925 08 May 09 nicklas 684     @see ProgressReporter
4925 08 May 09 nicklas 685   */
4925 08 May 09 nicklas 686   protected void setProgress(int percent, String message)
4925 08 May 09 nicklas 687   {
4925 08 May 09 nicklas 688     if (progress != null)
4925 08 May 09 nicklas 689     {
4925 08 May 09 nicklas 690       progress.display(percent, message);
4925 08 May 09 nicklas 691     }
4925 08 May 09 nicklas 692   }
4925 08 May 09 nicklas 693   // ----------------------------
4925 08 May 09 nicklas 694 }