extensions/net.sf.basedb.relax/trunk/src/net/sf/basedb/relax/plugins/CohortExporterPlugin.java

Code
Comments
Other
Rev Date Author Line
4553 28 Aug 17 nicklas 1 package net.sf.basedb.relax.plugins;
4553 28 Aug 17 nicklas 2
5510 18 Jun 19 nicklas 3 import java.io.IOException;
5510 18 Jun 19 nicklas 4 import java.io.OutputStream;
4553 28 Aug 17 nicklas 5 import java.io.OutputStreamWriter;
5510 18 Jun 19 nicklas 6 import java.nio.charset.StandardCharsets;
4553 28 Aug 17 nicklas 7 import java.util.ArrayList;
4553 28 Aug 17 nicklas 8 import java.util.Arrays;
4553 28 Aug 17 nicklas 9 import java.util.Collections;
4553 28 Aug 17 nicklas 10 import java.util.HashMap;
4553 28 Aug 17 nicklas 11 import java.util.HashSet;
4553 28 Aug 17 nicklas 12 import java.util.List;
4553 28 Aug 17 nicklas 13 import java.util.Map;
4553 28 Aug 17 nicklas 14 import java.util.Set;
4553 28 Aug 17 nicklas 15
5510 18 Jun 19 nicklas 16 import org.apache.poi.ss.usermodel.Workbook;
5510 18 Jun 19 nicklas 17 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
5510 18 Jun 19 nicklas 18 import org.apache.poi.xssf.usermodel.XSSFWorkbookType;
5510 18 Jun 19 nicklas 19
4553 28 Aug 17 nicklas 20 import net.sf.basedb.core.Annotatable;
4553 28 Aug 17 nicklas 21 import net.sf.basedb.core.AnnotationType;
4553 28 Aug 17 nicklas 22 import net.sf.basedb.core.BaseException;
4553 28 Aug 17 nicklas 23 import net.sf.basedb.core.BooleanParameterType;
4553 28 Aug 17 nicklas 24 import net.sf.basedb.core.DbControl;
4553 28 Aug 17 nicklas 25 import net.sf.basedb.core.Directory;
4553 28 Aug 17 nicklas 26 import net.sf.basedb.core.File;
4553 28 Aug 17 nicklas 27 import net.sf.basedb.core.Item;
4553 28 Aug 17 nicklas 28 import net.sf.basedb.core.ItemAlreadyExistsException;
4553 28 Aug 17 nicklas 29 import net.sf.basedb.core.ItemList;
4553 28 Aug 17 nicklas 30 import net.sf.basedb.core.ItemParameterType;
4553 28 Aug 17 nicklas 31 import net.sf.basedb.core.ItemQuery;
4553 28 Aug 17 nicklas 32 import net.sf.basedb.core.Path;
4553 28 Aug 17 nicklas 33 import net.sf.basedb.core.PathParameterType;
4554 29 Aug 17 nicklas 34 import net.sf.basedb.core.PermissionDeniedException;
4553 28 Aug 17 nicklas 35 import net.sf.basedb.core.PluginParameter;
4553 28 Aug 17 nicklas 36 import net.sf.basedb.core.ProgressReporter;
4553 28 Aug 17 nicklas 37 import net.sf.basedb.core.RequestInformation;
5508 18 Jun 19 nicklas 38 import net.sf.basedb.core.StringParameterType;
4553 28 Aug 17 nicklas 39 import net.sf.basedb.core.Job.ExecutionTime;
4553 28 Aug 17 nicklas 40 import net.sf.basedb.core.Listable;
4553 28 Aug 17 nicklas 41 import net.sf.basedb.core.Nameable;
4553 28 Aug 17 nicklas 42 import net.sf.basedb.core.plugin.AbstractPlugin;
4553 28 Aug 17 nicklas 43 import net.sf.basedb.core.plugin.GuiContext;
4553 28 Aug 17 nicklas 44 import net.sf.basedb.core.plugin.InteractivePlugin;
4553 28 Aug 17 nicklas 45 import net.sf.basedb.core.plugin.Request;
4553 28 Aug 17 nicklas 46 import net.sf.basedb.core.plugin.Response;
4553 28 Aug 17 nicklas 47 import net.sf.basedb.core.query.Expressions;
4553 28 Aug 17 nicklas 48 import net.sf.basedb.core.query.Hql;
4553 28 Aug 17 nicklas 49 import net.sf.basedb.core.query.Orders;
4553 28 Aug 17 nicklas 50 import net.sf.basedb.core.query.Restrictions;
4553 28 Aug 17 nicklas 51 import net.sf.basedb.core.signal.SignalHandler;
4553 28 Aug 17 nicklas 52 import net.sf.basedb.core.signal.SignalTarget;
4553 28 Aug 17 nicklas 53 import net.sf.basedb.core.signal.ThreadSignalHandler;
4553 28 Aug 17 nicklas 54 import net.sf.basedb.core.snapshot.AnnotationSnapshot;
4553 28 Aug 17 nicklas 55 import net.sf.basedb.core.snapshot.AnnotationTypeFilter;
4553 28 Aug 17 nicklas 56 import net.sf.basedb.core.snapshot.SnapshotManager;
4553 28 Aug 17 nicklas 57 import net.sf.basedb.relax.Relax;
5239 17 Jan 19 nicklas 58 import net.sf.basedb.relax.dao.Annotationtype;
5510 18 Jun 19 nicklas 59 import net.sf.basedb.util.FileUtil;
4553 28 Aug 17 nicklas 60 import net.sf.basedb.util.Values;
4553 28 Aug 17 nicklas 61 import net.sf.basedb.util.encode.ToSpaceEncoderDecoder;
5510 18 Jun 19 nicklas 62 import net.sf.basedb.util.excel.XlsxTableWriter;
5510 18 Jun 19 nicklas 63 import net.sf.basedb.util.excel.XlsxToCsvUtil;
4553 28 Aug 17 nicklas 64 import net.sf.basedb.util.export.TableWriter;
4553 28 Aug 17 nicklas 65
4553 28 Aug 17 nicklas 66 /**
4553 28 Aug 17 nicklas 67   Plug-in for generating cohort data files. Input us an item list which
4553 28 Aug 17 nicklas 68   must contain items of the same subtype. Additionally, the parent chain
4553 28 Aug 17 nicklas 69   of every item must match all other items. One file is produced for each
4553 28 Aug 17 nicklas 70   step in the chain from the root item up to the topmost parent. The file
4553 28 Aug 17 nicklas 71   is named automatically after the subtype of the items in each step.
4553 28 Aug 17 nicklas 72
4553 28 Aug 17 nicklas 73   Each file will export:
4553 28 Aug 17 nicklas 74   * Name of root item (except in the starting file)
4553 28 Aug 17 nicklas 75   * Name of current item
4553 28 Aug 17 nicklas 76   * All annotation values for the current item
4553 28 Aug 17 nicklas 77
4553 28 Aug 17 nicklas 78   @author nicklas
4553 28 Aug 17 nicklas 79   @since 1.1
4553 28 Aug 17 nicklas 80 */
4553 28 Aug 17 nicklas 81 public class CohortExporterPlugin 
4553 28 Aug 17 nicklas 82   extends AbstractPlugin
4553 28 Aug 17 nicklas 83   implements InteractivePlugin, SignalTarget
4553 28 Aug 17 nicklas 84 {
4553 28 Aug 17 nicklas 85   
4553 28 Aug 17 nicklas 86   private Set<Item> itemTypes = new HashSet<>(Arrays.asList(
4553 28 Aug 17 nicklas 87     Item.RAWBIOASSAY, Item.DERIVEDBIOASSAY, Item.EXTRACT, Item.SAMPLE, Item.BIOSOURCE));
4553 28 Aug 17 nicklas 88   
4553 28 Aug 17 nicklas 89   private RequestInformation configureExport;
4553 28 Aug 17 nicklas 90   
4553 28 Aug 17 nicklas 91   private ThreadSignalHandler signalHandler;
4553 28 Aug 17 nicklas 92   
4553 28 Aug 17 nicklas 93   public CohortExporterPlugin() 
4553 28 Aug 17 nicklas 94   {}
4553 28 Aug 17 nicklas 95
4553 28 Aug 17 nicklas 96   @Override
4553 28 Aug 17 nicklas 97   public boolean requiresConfiguration()
4553 28 Aug 17 nicklas 98   {
4553 28 Aug 17 nicklas 99     return false;
4553 28 Aug 17 nicklas 100   }
4553 28 Aug 17 nicklas 101
4553 28 Aug 17 nicklas 102   /*
4553 28 Aug 17 nicklas 103      From the Plugin interface
4553 28 Aug 17 nicklas 104      --------------------------------
4553 28 Aug 17 nicklas 105   */
4553 28 Aug 17 nicklas 106   @Override
4553 28 Aug 17 nicklas 107   public boolean supportsConfigurations()
4553 28 Aug 17 nicklas 108   {
4553 28 Aug 17 nicklas 109     return false;
4553 28 Aug 17 nicklas 110   }
4553 28 Aug 17 nicklas 111   @Override
4553 28 Aug 17 nicklas 112   public MainType getMainType() 
4553 28 Aug 17 nicklas 113   {
4553 28 Aug 17 nicklas 114     return MainType.EXPORT;
4553 28 Aug 17 nicklas 115   }
4553 28 Aug 17 nicklas 116
4553 28 Aug 17 nicklas 117   @Override
4553 28 Aug 17 nicklas 118   public void run(Request request, Response response, ProgressReporter progress) 
4553 28 Aug 17 nicklas 119   {
4553 28 Aug 17 nicklas 120     if (signalHandler != null) signalHandler.setWorkerThread(null);
4553 28 Aug 17 nicklas 121     
4553 28 Aug 17 nicklas 122     DbControl dc = null;
4553 28 Aug 17 nicklas 123     DbControl readerDc = null;
4553 28 Aug 17 nicklas 124     try
4553 28 Aug 17 nicklas 125     {
4553 28 Aug 17 nicklas 126       dc = sc.newDbControl();
4553 28 Aug 17 nicklas 127       
5508 18 Jun 19 nicklas 128       String fileFormat = (String)job.getValue("fileFormat");
4553 28 Aug 17 nicklas 129       String saveToPath = (String)job.getValue("saveTo");
4553 28 Aug 17 nicklas 130       boolean overwrite = Boolean.TRUE.equals(job.getValue("overwrite"));
5557 13 Aug 19 nicklas 131       boolean debugMode = Boolean.TRUE.equals(job.getValue("debugMode"));
5508 18 Jun 19 nicklas 132       
5510 18 Jun 19 nicklas 133       TableWriterFactory factory = null;
5508 18 Jun 19 nicklas 134       if ("XLSX".equals(fileFormat))
5508 18 Jun 19 nicklas 135       {
5508 18 Jun 19 nicklas 136         Path p = new Path(saveToPath, Path.Type.FILE);
5510 18 Jun 19 nicklas 137         Directory saveTo = Directory.getNew(dc, p);
5510 18 Jun 19 nicklas 138         File xlsx = File.getFile(dc, saveTo, p.getFilename(), true);
5510 18 Jun 19 nicklas 139         if (xlsx.isInDatabase() && !overwrite && !xlsx.isRemoved())
5508 18 Jun 19 nicklas 140         {  
5508 18 Jun 19 nicklas 141           // Not allowed to overwrite the existing file
5510 18 Jun 19 nicklas 142           throw new ItemAlreadyExistsException("File[path="+xlsx.getPath()+"]");      
5508 18 Jun 19 nicklas 143         }
5510 18 Jun 19 nicklas 144         else if (!xlsx.isInDatabase())
5508 18 Jun 19 nicklas 145         {
5510 18 Jun 19 nicklas 146           dc.saveItem(xlsx);
5508 18 Jun 19 nicklas 147         }
5510 18 Jun 19 nicklas 148         xlsx.setMimeType(XlsxToCsvUtil.XLSX_MIME_TYPE);
5510 18 Jun 19 nicklas 149         factory = new XlsxTableWriterFactory(xlsx);
5508 18 Jun 19 nicklas 150       }
5508 18 Jun 19 nicklas 151       else
5508 18 Jun 19 nicklas 152       {
5508 18 Jun 19 nicklas 153         // CSV
5510 18 Jun 19 nicklas 154         Directory saveTo = Directory.getNew(dc, new Path(saveToPath, Path.Type.DIRECTORY));
5510 18 Jun 19 nicklas 155         factory = new CsvTableWriterFactory(saveTo, overwrite);
5508 18 Jun 19 nicklas 156       }
5238 17 Jan 19 nicklas 157       boolean exportChildren = Boolean.TRUE.equals(job.getValue("exportChildren"));
4553 28 Aug 17 nicklas 158       
4553 28 Aug 17 nicklas 159       // Load the list...
4553 28 Aug 17 nicklas 160       ItemList list = (ItemList)job.getValue("itemList");
4553 28 Aug 17 nicklas 161       list = ItemList.getById(dc, list.getId());
4553 28 Aug 17 nicklas 162
4553 28 Aug 17 nicklas 163       // ...and all members (id only)
4553 28 Aug 17 nicklas 164       ItemQuery<? extends Listable> query = list.getMembers();
4553 28 Aug 17 nicklas 165       query.setIncludes(Relax.INCLUDE_IN_CURRENT_PROJECT);
4553 28 Aug 17 nicklas 166       query.order(Orders.asc(Hql.property("name")));
4553 28 Aug 17 nicklas 167       List<Integer> itemIds = query.idList(dc);
4553 28 Aug 17 nicklas 168       
4553 28 Aug 17 nicklas 169       Item memberType = list.getMemberType();
4553 28 Aug 17 nicklas 170       Map<String, CohortTableWriter> writers = new HashMap<>();
4553 28 Aug 17 nicklas 171       SnapshotManager manager = null;
4553 28 Aug 17 nicklas 172       int count = 0;
4553 28 Aug 17 nicklas 173       int totalCount = list.getSize();
5222 11 Jan 19 nicklas 174       Set<String> exportedItems = new HashSet<>();
4553 28 Aug 17 nicklas 175       for (int itemId : itemIds)
4553 28 Aug 17 nicklas 176       {
4553 28 Aug 17 nicklas 177         if (count % 100 == 0)
4553 28 Aug 17 nicklas 178         {
4553 28 Aug 17 nicklas 179           // Rollback and create new DbControl and Snapshot 
4553 28 Aug 17 nicklas 180           // Manager to allow GC to reclaim memory
4553 28 Aug 17 nicklas 181           if (readerDc != null) readerDc.close();
4553 28 Aug 17 nicklas 182           readerDc = dc.getSessionControl().newDbControl();
4553 28 Aug 17 nicklas 183           manager = new SnapshotManager();
4553 28 Aug 17 nicklas 184         }
4553 28 Aug 17 nicklas 185         if (count % 10 == 0)
4553 28 Aug 17 nicklas 186         {
4553 28 Aug 17 nicklas 187           ThreadSignalHandler.checkInterrupted();
4553 28 Aug 17 nicklas 188           if (progress != null)
4553 28 Aug 17 nicklas 189           {
4553 28 Aug 17 nicklas 190             progress.display(5 + (int)((90*count)/totalCount), 
4553 28 Aug 17 nicklas 191               "Exporting cohort data (" + count + " of " + totalCount + ")...");
4553 28 Aug 17 nicklas 192           }
4553 28 Aug 17 nicklas 193         }
4553 28 Aug 17 nicklas 194         count++;
4553 28 Aug 17 nicklas 195         
4553 28 Aug 17 nicklas 196         CohortItem rootItem = new CohortItem((Nameable)memberType.getById(readerDc, itemId));
4553 28 Aug 17 nicklas 197         CohortChain cohort = new CohortChain(readerDc, rootItem);
4553 28 Aug 17 nicklas 198         
4553 28 Aug 17 nicklas 199         List<CohortItem> parents = cohort.getParentItems();
4553 28 Aug 17 nicklas 200         for (CohortItem parent : parents)
4553 28 Aug 17 nicklas 201         {
5222 11 Jan 19 nicklas 202           if (!exportedItems.add(parent.getName())) continue; // Skipping duplicate
5222 11 Jan 19 nicklas 203           
4553 28 Aug 17 nicklas 204           String subtype = parent.getSubtype();
4553 28 Aug 17 nicklas 205           CohortTableWriter writer = writers.get(subtype);
5222 11 Jan 19 nicklas 206           if (writer == null)
5222 11 Jan 19 nicklas 207           {
5510 18 Jun 19 nicklas 208             writer = factory.createCohortWriter(dc, cohort, parent);
5222 11 Jan 19 nicklas 209             writers.put(subtype, writer);
5222 11 Jan 19 nicklas 210           }
5227 14 Jan 19 nicklas 211           writer.writeItem(parent, cohort, readerDc, manager);
4553 28 Aug 17 nicklas 212         }
5219 11 Jan 19 nicklas 213         
5238 17 Jan 19 nicklas 214         if (exportChildren)
5219 11 Jan 19 nicklas 215         {
5238 17 Jan 19 nicklas 216           List<CohortItem> children = cohort.getChildItems();
5238 17 Jan 19 nicklas 217           for (CohortItem child : children)
5219 11 Jan 19 nicklas 218           {
5238 17 Jan 19 nicklas 219             if (!exportedItems.add(child.getName())) continue;
5238 17 Jan 19 nicklas 220   
5238 17 Jan 19 nicklas 221             String subtype = child.getSubtype();
5238 17 Jan 19 nicklas 222             CohortTableWriter writer = writers.get(subtype);
5238 17 Jan 19 nicklas 223             if (writer == null)
5238 17 Jan 19 nicklas 224             {
5510 18 Jun 19 nicklas 225               writer = factory.createCohortWriter(dc, cohort, child);
5238 17 Jan 19 nicklas 226               writers.put(subtype, writer);
5238 17 Jan 19 nicklas 227             }
5238 17 Jan 19 nicklas 228             writer.writeItem(child, cohort, readerDc, manager);
5219 11 Jan 19 nicklas 229           }
5219 11 Jan 19 nicklas 230         }
4553 28 Aug 17 nicklas 231       }
4553 28 Aug 17 nicklas 232       
4553 28 Aug 17 nicklas 233       for (CohortTableWriter writer : writers.values())
4553 28 Aug 17 nicklas 234       {
4553 28 Aug 17 nicklas 235         writer.close();
4553 28 Aug 17 nicklas 236       }
5510 18 Jun 19 nicklas 237       factory.close();
4553 28 Aug 17 nicklas 238       
4553 28 Aug 17 nicklas 239       dc.commit();
4553 28 Aug 17 nicklas 240       String msg = totalCount + " items in the '" + list.getName() + "' list have been exported to " + saveToPath;
5557 13 Aug 19 nicklas 241       if (debugMode)
5557 13 Aug 19 nicklas 242       {
5557 13 Aug 19 nicklas 243         // DEBUG!! will make it easier to re-run a job with same settings
5557 13 Aug 19 nicklas 244         response.setError("[DEBUG] "+msg, null);
5557 13 Aug 19 nicklas 245       }
5557 13 Aug 19 nicklas 246       else
5557 13 Aug 19 nicklas 247       {
5557 13 Aug 19 nicklas 248         response.setDone(msg);
5557 13 Aug 19 nicklas 249       }
4553 28 Aug 17 nicklas 250     }
4553 28 Aug 17 nicklas 251     catch (Throwable t)
4553 28 Aug 17 nicklas 252     {
4553 28 Aug 17 nicklas 253       response.setError(t.getMessage(), Arrays.asList(t));
4553 28 Aug 17 nicklas 254     }
4553 28 Aug 17 nicklas 255     finally
4553 28 Aug 17 nicklas 256     {
5510 18 Jun 19 nicklas 257       
4553 28 Aug 17 nicklas 258       if (readerDc != null) readerDc.close();
4553 28 Aug 17 nicklas 259       if (dc != null) dc.close();
4553 28 Aug 17 nicklas 260     }
4553 28 Aug 17 nicklas 261     
4553 28 Aug 17 nicklas 262   }
4553 28 Aug 17 nicklas 263   // -------------------------------------
4553 28 Aug 17 nicklas 264   /*
4553 28 Aug 17 nicklas 265      From the InteractivePlugin interface
4553 28 Aug 17 nicklas 266      -------------------------------------------
4553 28 Aug 17 nicklas 267   */
4553 28 Aug 17 nicklas 268   /**
4553 28 Aug 17 nicklas 269     The plug-in will appear on the single-item view
4553 28 Aug 17 nicklas 270     for item lists.
4553 28 Aug 17 nicklas 271    */
4553 28 Aug 17 nicklas 272   @Override
4553 28 Aug 17 nicklas 273   public Set<GuiContext> getGuiContexts()
4553 28 Aug 17 nicklas 274   {
4553 28 Aug 17 nicklas 275     return Collections.singleton(GuiContext.item(Item.ITEMLIST));
4553 28 Aug 17 nicklas 276   }
4553 28 Aug 17 nicklas 277   /**
4553 28 Aug 17 nicklas 278     We accept all item lists with:
4553 28 Aug 17 nicklas 279     * Raw bioassays
4553 28 Aug 17 nicklas 280     * Derived bioassays
4553 28 Aug 17 nicklas 281     * Extracts
4553 28 Aug 17 nicklas 282     * Samples
4553 28 Aug 17 nicklas 283     * Biosources
4553 28 Aug 17 nicklas 284     The list must have at least one member.
4553 28 Aug 17 nicklas 285   */
4553 28 Aug 17 nicklas 286   @Override
4553 28 Aug 17 nicklas 287   public String isInContext(GuiContext context, Object item)
4553 28 Aug 17 nicklas 288   {
4553 28 Aug 17 nicklas 289     String message = null;
4553 28 Aug 17 nicklas 290     if (item == null)
4553 28 Aug 17 nicklas 291     {
4553 28 Aug 17 nicklas 292       message = "The object is null";
4553 28 Aug 17 nicklas 293     }
4553 28 Aug 17 nicklas 294     else if (!(item instanceof ItemList))
4553 28 Aug 17 nicklas 295     {
4553 28 Aug 17 nicklas 296       message = "The object is not an item list: " + item;
4553 28 Aug 17 nicklas 297     }
4553 28 Aug 17 nicklas 298     ItemList list = (ItemList)item;
4553 28 Aug 17 nicklas 299     if (!itemTypes.contains(list.getMemberType()))
4553 28 Aug 17 nicklas 300     {
4554 29 Aug 17 nicklas 301       throw new BaseException("This plug-in need an item list with one of: " + Values.getString(itemTypes, ", ", true));
4553 28 Aug 17 nicklas 302     }
4553 28 Aug 17 nicklas 303     else if (list.getSize() == 0)
4553 28 Aug 17 nicklas 304     {
4554 29 Aug 17 nicklas 305       throw new BaseException("The selected list is empty.");
4553 28 Aug 17 nicklas 306     }
4554 29 Aug 17 nicklas 307     else if (sc.getActiveProjectId() == 0)
4554 29 Aug 17 nicklas 308     {
4554 29 Aug 17 nicklas 309       throw new BaseException("This plug-in requires an active project!");
4554 29 Aug 17 nicklas 310     }
4553 28 Aug 17 nicklas 311     return message;
4553 28 Aug 17 nicklas 312   }
4553 28 Aug 17 nicklas 313   @Override
4553 28 Aug 17 nicklas 314   public RequestInformation getRequestInformation(GuiContext context, String command) 
4553 28 Aug 17 nicklas 315     throws BaseException
4553 28 Aug 17 nicklas 316   {
4553 28 Aug 17 nicklas 317     RequestInformation requestInformation = null;
4553 28 Aug 17 nicklas 318     if (Request.COMMAND_CONFIGURE_JOB.equals(command))
4553 28 Aug 17 nicklas 319     {
4554 29 Aug 17 nicklas 320       if (sc.getActiveProjectId() == 0)
4554 29 Aug 17 nicklas 321       {
4554 29 Aug 17 nicklas 322         throw new PermissionDeniedException("This plug-in requires an active project!");
4554 29 Aug 17 nicklas 323       }
4553 28 Aug 17 nicklas 324       requestInformation = getConfigureExportParameters();
4553 28 Aug 17 nicklas 325     }
4553 28 Aug 17 nicklas 326     return requestInformation;
4553 28 Aug 17 nicklas 327   }
4553 28 Aug 17 nicklas 328   
4553 28 Aug 17 nicklas 329   @Override
4553 28 Aug 17 nicklas 330   public void configure(GuiContext context, Request request, Response response) 
4553 28 Aug 17 nicklas 331   {
4553 28 Aug 17 nicklas 332     String command = request.getCommand();    
4553 28 Aug 17 nicklas 333     try
4553 28 Aug 17 nicklas 334     {
4553 28 Aug 17 nicklas 335       if (command.equals(Request.COMMAND_CONFIGURE_JOB))
4553 28 Aug 17 nicklas 336       {
4553 28 Aug 17 nicklas 337         RequestInformation ri = getConfigureExportParameters();
4553 28 Aug 17 nicklas 338         List<Throwable> errors = validateRequestParameters(ri.getParameters(), request);
4553 28 Aug 17 nicklas 339         if (errors != null)
4553 28 Aug 17 nicklas 340         {
4553 28 Aug 17 nicklas 341           response.setError(errors.size() + " invalid parameters were found in the request", errors);
4553 28 Aug 17 nicklas 342           return;
4553 28 Aug 17 nicklas 343         }
4553 28 Aug 17 nicklas 344         
4553 28 Aug 17 nicklas 345         // We need to verify that all items in the list are of the same subtype
4553 28 Aug 17 nicklas 346         ItemList list = (ItemList)request.getParameterValue("itemList");
4553 28 Aug 17 nicklas 347         String error = validateList(list);
4553 28 Aug 17 nicklas 348         if (error != null)
4553 28 Aug 17 nicklas 349         {
4553 28 Aug 17 nicklas 350           response.setError(error, null);
4553 28 Aug 17 nicklas 351           return;
4553 28 Aug 17 nicklas 352         }
4553 28 Aug 17 nicklas 353         
4553 28 Aug 17 nicklas 354         String path = (String)request.getParameterValue("saveTo");
4553 28 Aug 17 nicklas 355         storeValue(job, request, ri.getParameter("itemList"));
5238 17 Jan 19 nicklas 356         storeValue(job, request, ri.getParameter("exportChildren"));
5508 18 Jun 19 nicklas 357         storeValue(job, request, ri.getParameter("fileFormat"));
4553 28 Aug 17 nicklas 358         storeValue(job, request, ri.getParameter("saveTo"));
4553 28 Aug 17 nicklas 359         storeValue(job, request, ri.getParameter("overwrite"));
5557 13 Aug 19 nicklas 360         storeValue(job, request, ri.getParameter("debugMode"));
4553 28 Aug 17 nicklas 361         
4553 28 Aug 17 nicklas 362         response.setSuggestedJobName("Cohort export for '" + list.getName() + "' to " + path);
4553 28 Aug 17 nicklas 363         response.setDone("The job configuration is complete", ExecutionTime.MEDIUM);
4553 28 Aug 17 nicklas 364       }
4553 28 Aug 17 nicklas 365     }
4553 28 Aug 17 nicklas 366     catch (Throwable ex)
4553 28 Aug 17 nicklas 367     {
4553 28 Aug 17 nicklas 368       response.setError(ex.getMessage(), Arrays.asList(ex));
4553 28 Aug 17 nicklas 369     }
4553 28 Aug 17 nicklas 370   }
4553 28 Aug 17 nicklas 371   
4553 28 Aug 17 nicklas 372   /**
4553 28 Aug 17 nicklas 373     Check that all items in the list are of the same subtype.
4553 28 Aug 17 nicklas 374     This is an initial check only which should be fairly quick.
4553 28 Aug 17 nicklas 375     During the export, additional checks are made to ensure that 
4553 28 Aug 17 nicklas 376     all parent items are following the same path up to the topmost 
4553 28 Aug 17 nicklas 377     parent.
4553 28 Aug 17 nicklas 378   */
4553 28 Aug 17 nicklas 379   private String validateList(ItemList list)
4553 28 Aug 17 nicklas 380   {
4553 28 Aug 17 nicklas 381     DbControl dc = null;
4553 28 Aug 17 nicklas 382     try
4553 28 Aug 17 nicklas 383     {
4553 28 Aug 17 nicklas 384       dc = sc.newDbControl();
4553 28 Aug 17 nicklas 385       ItemQuery<? extends Listable> query = list.getMembers();
4553 28 Aug 17 nicklas 386       query.setIncludes(Relax.INCLUDE_IN_CURRENT_PROJECT);
4553 28 Aug 17 nicklas 387       query.order(Orders.asc(Hql.property("name")));
4553 28 Aug 17 nicklas 388       
4553 28 Aug 17 nicklas 389       List<? extends Listable> items = query.list(dc);
5246 18 Jan 19 nicklas 390       if (items.size() == 0) return "The '"+list.getName()+"' list is empty.";
4553 28 Aug 17 nicklas 391       
5246 18 Jan 19 nicklas 392       if (list.getSize() != items.size())
5246 18 Jan 19 nicklas 393       {
5246 18 Jan 19 nicklas 394         int notInProject = list.getSize() - items.size();
5246 18 Jan 19 nicklas 395         return "The '"+list.getName()+"' list contains "+notInProject+" items that are not shared to the current project.";
5246 18 Jan 19 nicklas 396       }
5246 18 Jan 19 nicklas 397       
4553 28 Aug 17 nicklas 398       for (Listable item : items)
4553 28 Aug 17 nicklas 399       {
4553 28 Aug 17 nicklas 400         CohortItem ch = new CohortItem(item);
4553 28 Aug 17 nicklas 401         String subtype = ch.getSubtype();
4553 28 Aug 17 nicklas 402         if (subtype == null) return "Missing subtype on item '" + item.getName() + "'.";
4553 28 Aug 17 nicklas 403       }
4553 28 Aug 17 nicklas 404     }
4553 28 Aug 17 nicklas 405     finally
4553 28 Aug 17 nicklas 406     {
4553 28 Aug 17 nicklas 407       if (dc != null) dc.close();
4553 28 Aug 17 nicklas 408     }
4553 28 Aug 17 nicklas 409     return null;
4553 28 Aug 17 nicklas 410   }
4553 28 Aug 17 nicklas 411   
4553 28 Aug 17 nicklas 412   
4553 28 Aug 17 nicklas 413   /*
4553 28 Aug 17 nicklas 414     From the SignalTarget interface
4553 28 Aug 17 nicklas 415     -------------------------------------------
4553 28 Aug 17 nicklas 416   */
4553 28 Aug 17 nicklas 417   @Override
4553 28 Aug 17 nicklas 418   public SignalHandler getSignalHandler()
4553 28 Aug 17 nicklas 419   {
4553 28 Aug 17 nicklas 420     signalHandler = new ThreadSignalHandler();
4553 28 Aug 17 nicklas 421     return signalHandler;
4553 28 Aug 17 nicklas 422   }
4553 28 Aug 17 nicklas 423   // -------------------------------------------
4553 28 Aug 17 nicklas 424
4553 28 Aug 17 nicklas 425   
4553 28 Aug 17 nicklas 426   private RequestInformation getConfigureExportParameters()
4553 28 Aug 17 nicklas 427   {
4553 28 Aug 17 nicklas 428     if (configureExport == null)
4553 28 Aug 17 nicklas 429     {
4553 28 Aug 17 nicklas 430       // Load the current item list
4553 28 Aug 17 nicklas 431       ItemList currentList = null;
5238 17 Jan 19 nicklas 432       Item listType = null;
4553 28 Aug 17 nicklas 433       int currentListId = sc.getCurrentContext(Item.ITEMLIST).getId();
4553 28 Aug 17 nicklas 434       if (currentListId != 0)
4553 28 Aug 17 nicklas 435       {
4553 28 Aug 17 nicklas 436         DbControl dc = sc.newDbControl();
4553 28 Aug 17 nicklas 437         try
4553 28 Aug 17 nicklas 438         {
4553 28 Aug 17 nicklas 439           currentList = ItemList.getById(dc, currentListId);
5238 17 Jan 19 nicklas 440           listType = currentList.getMemberType();
4553 28 Aug 17 nicklas 441         }
4553 28 Aug 17 nicklas 442         finally
4553 28 Aug 17 nicklas 443         {
4553 28 Aug 17 nicklas 444           if (dc != null) dc.close();
4553 28 Aug 17 nicklas 445         }
4553 28 Aug 17 nicklas 446       }
4553 28 Aug 17 nicklas 447       
4553 28 Aug 17 nicklas 448       List<PluginParameter<?>> parameters = new ArrayList<PluginParameter<?>>();
4553 28 Aug 17 nicklas 449
4553 28 Aug 17 nicklas 450       parameters.add(new PluginParameter<ItemList>(
4553 28 Aug 17 nicklas 451         "itemList", "Item list", "Select the list with items to export. "+
5238 17 Jan 19 nicklas 452         "Parent items are always included in the export.", 
4553 28 Aug 17 nicklas 453         new ItemParameterType<ItemList>(ItemList.class, currentList, true, 1, null)
4553 28 Aug 17 nicklas 454       ));
4553 28 Aug 17 nicklas 455       
5238 17 Jan 19 nicklas 456       if (listType != Item.RAWBIOASSAY)
5238 17 Jan 19 nicklas 457       {
5238 17 Jan 19 nicklas 458         parameters.add(new PluginParameter<Boolean>(
5238 17 Jan 19 nicklas 459           "exportChildren", "Export child items", "If this option is selected child items are also exported.",
5238 17 Jan 19 nicklas 460           new BooleanParameterType(listType == Item.BIOSOURCE ? true : null, false)
5238 17 Jan 19 nicklas 461         ));
5238 17 Jan 19 nicklas 462       }
5238 17 Jan 19 nicklas 463       
4553 28 Aug 17 nicklas 464       parameters.add(new PluginParameter<String>(
5508 18 Jun 19 nicklas 465         "fileFormat", "File format", "Select which file format to export to. If CSV is selected, " +
5508 18 Jun 19 nicklas 466         "the data is saved to multiple CSV files in the 'Save to' directory. If XLSX is selected, " +
5508 18 Jun 19 nicklas 467         "the data is saved to multiple worksheets in a single Excel file.",
5508 18 Jun 19 nicklas 468         new StringParameterType(255, "CSV", true, 1, 0, 0, Arrays.asList("CSV", "XLSX"))
5508 18 Jun 19 nicklas 469       ));
5508 18 Jun 19 nicklas 470       
5508 18 Jun 19 nicklas 471       parameters.add(new PluginParameter<String>(
4553 28 Aug 17 nicklas 472         "saveTo", "Save to", "Select the location where files should be saved.",
5508 18 Jun 19 nicklas 473         new PathParameterType(Path.Type.FILE, null, true)
4553 28 Aug 17 nicklas 474       ));
4553 28 Aug 17 nicklas 475         
4553 28 Aug 17 nicklas 476       parameters.add(new PluginParameter<Boolean>(
4553 28 Aug 17 nicklas 477         "overwrite", "Overwrite", "Is it allowed to overwrite existing files or not?",
4553 28 Aug 17 nicklas 478         new BooleanParameterType(null, false)
4553 28 Aug 17 nicklas 479       ));
5557 13 Aug 19 nicklas 480       
5557 13 Aug 19 nicklas 481       // If <developer-mode>1</developer-mode> is in relax-config.xml we allow debug via parameter
5557 13 Aug 19 nicklas 482       if (Values.getBoolean(Relax.getConfig().getConfig("developer-mode")))
5557 13 Aug 19 nicklas 483       {
5557 13 Aug 19 nicklas 484         parameters.add(new PluginParameter<Boolean>(
5557 13 Aug 19 nicklas 485           "debugMode", "Debug mode", "Runs the plug-in in debug mode. Major difference is "
5557 13 Aug 19 nicklas 486               + "that it will always report an error making it easier to re-start the "
5557 13 Aug 19 nicklas 487               + "plug-in with the same parameters.",
5557 13 Aug 19 nicklas 488           new BooleanParameterType(null, false)
5557 13 Aug 19 nicklas 489           ));
5557 13 Aug 19 nicklas 490       }
5557 13 Aug 19 nicklas 491
4553 28 Aug 17 nicklas 492       configureExport = new RequestInformation
4553 28 Aug 17 nicklas 493       (
4553 28 Aug 17 nicklas 494         Request.COMMAND_CONFIGURE_JOB,
4553 28 Aug 17 nicklas 495         "Cohort exporter options",
4553 28 Aug 17 nicklas 496         "Select the item list and where to save the exported data files.",
4553 28 Aug 17 nicklas 497         parameters
4553 28 Aug 17 nicklas 498       );
4553 28 Aug 17 nicklas 499     }
4553 28 Aug 17 nicklas 500     return configureExport;
4553 28 Aug 17 nicklas 501   }
4553 28 Aug 17 nicklas 502
5510 18 Jun 19 nicklas 503   /**
5510 18 Jun 19 nicklas 504     Factories are used to create CohortTableWriter and the underlying TableWriter. Typically
5510 18 Jun 19 nicklas 505     we have one writer per item subtype.
5510 18 Jun 19 nicklas 506   */
5510 18 Jun 19 nicklas 507   static abstract class TableWriterFactory
5510 18 Jun 19 nicklas 508     implements AutoCloseable
4553 28 Aug 17 nicklas 509   {
5227 14 Jan 19 nicklas 510     
5510 18 Jun 19 nicklas 511     CohortTableWriter createCohortWriter(DbControl dc, CohortChain chain, CohortItem item)
5510 18 Jun 19 nicklas 512     {
5510 18 Jun 19 nicklas 513       String subtype = item.getSubtype();
5510 18 Jun 19 nicklas 514       TableWriter out = createTableWriter(dc, subtype);
5510 18 Jun 19 nicklas 515       
5510 18 Jun 19 nicklas 516       Nameable parent = item.getParentItem(dc);
5510 18 Jun 19 nicklas 517       String parentSubtype = parent != null ? new CohortItem(parent).getSubtype() : null;
5510 18 Jun 19 nicklas 518       CohortItem topItem = chain.getTopItem();
5510 18 Jun 19 nicklas 519       String topSubtype = topItem != null ? topItem.getSubtype() : null;
5510 18 Jun 19 nicklas 520       
5510 18 Jun 19 nicklas 521       CohortTableWriter writer = new CohortTableWriter(dc, item.getType(), subtype, parentSubtype, topSubtype, out);    
5510 18 Jun 19 nicklas 522       return writer;
4553 28 Aug 17 nicklas 523     }
5510 18 Jun 19 nicklas 524     
5510 18 Jun 19 nicklas 525     abstract TableWriter createTableWriter(DbControl dc, String subtype);
5510 18 Jun 19 nicklas 526   }
5510 18 Jun 19 nicklas 527   
5510 18 Jun 19 nicklas 528   /**
5510 18 Jun 19 nicklas 529     This writer factory directs written data to CSV files. One file is created per 
5510 18 Jun 19 nicklas 530     item subtype in a given directory.
5510 18 Jun 19 nicklas 531   */
5510 18 Jun 19 nicklas 532   static class CsvTableWriterFactory
5510 18 Jun 19 nicklas 533     extends TableWriterFactory
5510 18 Jun 19 nicklas 534   {
5510 18 Jun 19 nicklas 535
5510 18 Jun 19 nicklas 536     private final Directory saveTo;
5510 18 Jun 19 nicklas 537     private final boolean overwrite;
5510 18 Jun 19 nicklas 538     
5510 18 Jun 19 nicklas 539     public CsvTableWriterFactory(Directory saveTo, boolean overwrite) 
4553 28 Aug 17 nicklas 540     {
5510 18 Jun 19 nicklas 541       this.saveTo = saveTo;
5510 18 Jun 19 nicklas 542       this.overwrite = overwrite;
4553 28 Aug 17 nicklas 543     }
5510 18 Jun 19 nicklas 544     
5510 18 Jun 19 nicklas 545     @Override
5510 18 Jun 19 nicklas 546     TableWriter createTableWriter(DbControl dc, String subtype) 
5510 18 Jun 19 nicklas 547     {
5510 18 Jun 19 nicklas 548       File file = File.getFile(dc, saveTo, subtype.toLowerCase()+".txt", true);
5510 18 Jun 19 nicklas 549       if (file.isInDatabase() && !overwrite && !file.isRemoved())
5510 18 Jun 19 nicklas 550       {  
5510 18 Jun 19 nicklas 551         // Not allowed to overwrite the existing file
5510 18 Jun 19 nicklas 552         throw new ItemAlreadyExistsException("File[path="+file.getPath()+"]");      
5510 18 Jun 19 nicklas 553       }
5510 18 Jun 19 nicklas 554       else if (!file.isInDatabase())
5510 18 Jun 19 nicklas 555       {
5510 18 Jun 19 nicklas 556         dc.saveItem(file);
5510 18 Jun 19 nicklas 557       }
5510 18 Jun 19 nicklas 558       file.setCharacterSet("UTF-8");
5510 18 Jun 19 nicklas 559       file.setMimeType("text/plain");
5510 18 Jun 19 nicklas 560       return new TableWriter(new OutputStreamWriter(file.getUploadStream(false), StandardCharsets.UTF_8));
5510 18 Jun 19 nicklas 561     }
4553 28 Aug 17 nicklas 562
5510 18 Jun 19 nicklas 563     // Nothing to do
5510 18 Jun 19 nicklas 564     @Override
5510 18 Jun 19 nicklas 565     public void close() 
5510 18 Jun 19 nicklas 566     {}
4553 28 Aug 17 nicklas 567   }
4553 28 Aug 17 nicklas 568   
5510 18 Jun 19 nicklas 569   /**
5510 18 Jun 19 nicklas 570     This writer factory directs written data to worksheets in an Excel file.
5510 18 Jun 19 nicklas 571     One worksheet is created per item subtype.
5510 18 Jun 19 nicklas 572   */
5510 18 Jun 19 nicklas 573   static class XlsxTableWriterFactory
5510 18 Jun 19 nicklas 574     extends TableWriterFactory
5510 18 Jun 19 nicklas 575   {
5510 18 Jun 19 nicklas 576
5510 18 Jun 19 nicklas 577     private final Workbook workbook;
5510 18 Jun 19 nicklas 578     private final File xlsx;
5510 18 Jun 19 nicklas 579
5510 18 Jun 19 nicklas 580     XlsxTableWriterFactory(File xlsx)
5510 18 Jun 19 nicklas 581     {
5510 18 Jun 19 nicklas 582       this.xlsx = xlsx;
5510 18 Jun 19 nicklas 583       workbook = new XSSFWorkbook(XSSFWorkbookType.XLSX);
5510 18 Jun 19 nicklas 584     }
5510 18 Jun 19 nicklas 585     
5510 18 Jun 19 nicklas 586     @Override
5510 18 Jun 19 nicklas 587     TableWriter createTableWriter(DbControl dc, String subtype) 
5510 18 Jun 19 nicklas 588     {
5510 18 Jun 19 nicklas 589       return new XlsxTableWriter(workbook, subtype);
5510 18 Jun 19 nicklas 590     }
5510 18 Jun 19 nicklas 591     
5510 18 Jun 19 nicklas 592     /**
5510 18 Jun 19 nicklas 593       Write the workbook to the file that was given at construction.
5510 18 Jun 19 nicklas 594     */
5510 18 Jun 19 nicklas 595     @Override
5510 18 Jun 19 nicklas 596     public void close()
5510 18 Jun 19 nicklas 597       throws IOException
5510 18 Jun 19 nicklas 598     {
5510 18 Jun 19 nicklas 599       OutputStream out = null;
5510 18 Jun 19 nicklas 600       try
5510 18 Jun 19 nicklas 601       {
5510 18 Jun 19 nicklas 602         out = xlsx.getUploadStream(false);
5510 18 Jun 19 nicklas 603         workbook.write(out);
5510 18 Jun 19 nicklas 604       }
5510 18 Jun 19 nicklas 605       finally
5510 18 Jun 19 nicklas 606       {
5510 18 Jun 19 nicklas 607         FileUtil.close(out);
5510 18 Jun 19 nicklas 608         FileUtil.close(workbook);
5510 18 Jun 19 nicklas 609       }
5510 18 Jun 19 nicklas 610     }
5510 18 Jun 19 nicklas 611   }
5510 18 Jun 19 nicklas 612   
4553 28 Aug 17 nicklas 613   static class CohortTableWriter
5510 18 Jun 19 nicklas 614     implements AutoCloseable
4553 28 Aug 17 nicklas 615   {
5510 18 Jun 19 nicklas 616     private final TableWriter out;
4553 28 Aug 17 nicklas 617     private final Object[] data;
4553 28 Aug 17 nicklas 618     private final String thisSubtype;
5220 11 Jan 19 nicklas 619     private final String parentSubtype;
4769 18 Apr 18 nicklas 620     private final List<ColumnWriter> colWriters;
4553 28 Aug 17 nicklas 621     
5510 18 Jun 19 nicklas 622     CohortTableWriter(DbControl dc, Item itemType, String subtype, String parentSubtype, String topSubtype, TableWriter out)
4553 28 Aug 17 nicklas 623     {
5510 18 Jun 19 nicklas 624       this.out = out;
4553 28 Aug 17 nicklas 625       this.thisSubtype = subtype;
5220 11 Jan 19 nicklas 626       this.parentSubtype = parentSubtype;
4553 28 Aug 17 nicklas 627       
5510 18 Jun 19 nicklas 628       out.setDataSeparator("\t"); // Tab-separated file
5510 18 Jun 19 nicklas 629       out.setNullValue("");
4553 28 Aug 17 nicklas 630       // Get rid of "bad" characters (tab, newline, etc.)
5510 18 Jun 19 nicklas 631       out.setEncoder(new ToSpaceEncoderDecoder());
4553 28 Aug 17 nicklas 632
4769 18 Apr 18 nicklas 633       // Create column writers
4769 18 Apr 18 nicklas 634       this.colWriters = new ArrayList<>();
5220 11 Jan 19 nicklas 635       // The name of the current item
5228 14 Jan 19 nicklas 636       colWriters.add(new ItemNameColumn(thisSubtype + "Name"));
5220 11 Jan 19 nicklas 637       if (parentSubtype != null)
4769 18 Apr 18 nicklas 638       {
5220 11 Jan 19 nicklas 639         // The name of the parent item
5228 14 Jan 19 nicklas 640         colWriters.add(new ParentItemNameColumn(parentSubtype + "Name"));
5220 11 Jan 19 nicklas 641       }
5227 14 Jan 19 nicklas 642       if (topSubtype != null && !topSubtype.equals(parentSubtype) && !topSubtype.equals(subtype))
5227 14 Jan 19 nicklas 643       {
5227 14 Jan 19 nicklas 644         // The name of the top item
5228 14 Jan 19 nicklas 645         colWriters.add(new TopItemNameColumn(topSubtype + "Name"));
5227 14 Jan 19 nicklas 646       }
4769 18 Apr 18 nicklas 647
5239 17 Jan 19 nicklas 648       // The FirstReleasedIn annotation
5239 17 Jan 19 nicklas 649       AnnotationType firstReleasedIn = Annotationtype.FIRST_RELEASED_IN.load(dc);
5239 17 Jan 19 nicklas 650       colWriters.add(new AnnotationColumn(firstReleasedIn));
5239 17 Jan 19 nicklas 651       
4769 18 Apr 18 nicklas 652       // Then, all annotation types defined for the current subtype
4553 28 Aug 17 nicklas 653       ItemQuery<AnnotationType> query = AnnotationType.getQuery(itemType);
4553 28 Aug 17 nicklas 654       query.setIncludes(Relax.INCLUDE_IN_CURRENT_PROJECT);
5223 11 Jan 19 nicklas 655       if (subtype != null)
4553 28 Aug 17 nicklas 656       {
4553 28 Aug 17 nicklas 657         query.join(Hql.innerJoin("categories", "cat"));
4553 28 Aug 17 nicklas 658         query.restrict(Restrictions.eq(Hql.property("cat", "name"), Expressions.string(subtype)));
4553 28 Aug 17 nicklas 659       }
4553 28 Aug 17 nicklas 660       query.order(Orders.asc(Hql.property("name")));
4553 28 Aug 17 nicklas 661       List<AnnotationType> list = query.list(dc);
4553 28 Aug 17 nicklas 662       for (AnnotationType at : list)
4553 28 Aug 17 nicklas 663       {
5239 17 Jan 19 nicklas 664         if (at.equals(firstReleasedIn)) continue; // Avoid duplicate columns
4769 18 Apr 18 nicklas 665         colWriters.add(new AnnotationColumn(at));
4553 28 Aug 17 nicklas 666       }
4769 18 Apr 18 nicklas 667
4769 18 Apr 18 nicklas 668       data = new Object[colWriters.size()];
4769 18 Apr 18 nicklas 669       writeHeaders();
4553 28 Aug 17 nicklas 670     }
4553 28 Aug 17 nicklas 671     
4769 18 Apr 18 nicklas 672     /**
4769 18 Apr 18 nicklas 673       Write the headers.
4769 18 Apr 18 nicklas 674     */
4769 18 Apr 18 nicklas 675     private void writeHeaders()
4553 28 Aug 17 nicklas 676     {
4553 28 Aug 17 nicklas 677       int col = 0;
4769 18 Apr 18 nicklas 678       for (ColumnWriter colWriter : colWriters)
4553 28 Aug 17 nicklas 679       {
4769 18 Apr 18 nicklas 680         data[col] = colWriter.getHeader();
4553 28 Aug 17 nicklas 681         col++;
4553 28 Aug 17 nicklas 682       }
5510 18 Jun 19 nicklas 683       out.tablePrintHeaders(data);
4553 28 Aug 17 nicklas 684     }
4553 28 Aug 17 nicklas 685     
4769 18 Apr 18 nicklas 686     /**
4769 18 Apr 18 nicklas 687       Write values for the current item.
4769 18 Apr 18 nicklas 688     */
5227 14 Jan 19 nicklas 689     void writeItem(CohortItem item, CohortChain chain, DbControl dc, SnapshotManager manager)
4553 28 Aug 17 nicklas 690     {
4553 28 Aug 17 nicklas 691       int col = 0;
4769 18 Apr 18 nicklas 692       for (ColumnWriter colWriter : colWriters)
4553 28 Aug 17 nicklas 693       {
5227 14 Jan 19 nicklas 694         data[col] = colWriter.getValue(item, chain, dc, manager);
4553 28 Aug 17 nicklas 695         col++;
4553 28 Aug 17 nicklas 696       }
5510 18 Jun 19 nicklas 697       out.tablePrintData(data);
4553 28 Aug 17 nicklas 698     }
5510 18 Jun 19 nicklas 699
5510 18 Jun 19 nicklas 700     @Override
5510 18 Jun 19 nicklas 701     public void close() 
5510 18 Jun 19 nicklas 702     {
5510 18 Jun 19 nicklas 703       out.close();
5510 18 Jun 19 nicklas 704     }
4553 28 Aug 17 nicklas 705     
4553 28 Aug 17 nicklas 706   }
4553 28 Aug 17 nicklas 707   
4769 18 Apr 18 nicklas 708   /**
4769 18 Apr 18 nicklas 709     Get value for an exported column.
4769 18 Apr 18 nicklas 710   */
4769 18 Apr 18 nicklas 711   static interface ColumnWriter
4769 18 Apr 18 nicklas 712   {
4769 18 Apr 18 nicklas 713     /**
4769 18 Apr 18 nicklas 714       Column header.
4769 18 Apr 18 nicklas 715     */
4769 18 Apr 18 nicklas 716     String getHeader();
4769 18 Apr 18 nicklas 717     
4769 18 Apr 18 nicklas 718     /**
5227 14 Jan 19 nicklas 719       Get the value from the specified item in the chain.
4769 18 Apr 18 nicklas 720     */
5227 14 Jan 19 nicklas 721     Object getValue(CohortItem item, CohortChain chain, DbControl dc, SnapshotManager manager);
4769 18 Apr 18 nicklas 722   }
4769 18 Apr 18 nicklas 723   
4769 18 Apr 18 nicklas 724   /**
4769 18 Apr 18 nicklas 725     Get the name of the current item.
4769 18 Apr 18 nicklas 726   */
4769 18 Apr 18 nicklas 727   static class ItemNameColumn
4769 18 Apr 18 nicklas 728     implements ColumnWriter
4769 18 Apr 18 nicklas 729   {
4769 18 Apr 18 nicklas 730     private final String header;
4769 18 Apr 18 nicklas 731     ItemNameColumn(String header)
4769 18 Apr 18 nicklas 732     {
4769 18 Apr 18 nicklas 733       this.header = header;
4769 18 Apr 18 nicklas 734     }
4769 18 Apr 18 nicklas 735
4769 18 Apr 18 nicklas 736     @Override
4769 18 Apr 18 nicklas 737     public String getHeader() 
4769 18 Apr 18 nicklas 738     {
4769 18 Apr 18 nicklas 739       return header;
4769 18 Apr 18 nicklas 740     }
4769 18 Apr 18 nicklas 741
4769 18 Apr 18 nicklas 742     @Override
5227 14 Jan 19 nicklas 743     public Object getValue(CohortItem item, CohortChain chain, DbControl dc, SnapshotManager manager) 
4769 18 Apr 18 nicklas 744     {
4769 18 Apr 18 nicklas 745       return item.getName();
4769 18 Apr 18 nicklas 746     }
4769 18 Apr 18 nicklas 747   }
4769 18 Apr 18 nicklas 748   
4769 18 Apr 18 nicklas 749   /**
4769 18 Apr 18 nicklas 750     Get the name of the root item.
4769 18 Apr 18 nicklas 751   */
4769 18 Apr 18 nicklas 752   static class RootItemNameColumn
4769 18 Apr 18 nicklas 753     extends ItemNameColumn
4769 18 Apr 18 nicklas 754   {
4769 18 Apr 18 nicklas 755     RootItemNameColumn(String header)
4769 18 Apr 18 nicklas 756     {
4769 18 Apr 18 nicklas 757       super(header);
4769 18 Apr 18 nicklas 758     }
4769 18 Apr 18 nicklas 759   
4769 18 Apr 18 nicklas 760     @Override
5227 14 Jan 19 nicklas 761     public Object getValue(CohortItem item, CohortChain chain, DbControl dc, SnapshotManager manager) 
4769 18 Apr 18 nicklas 762     {
5227 14 Jan 19 nicklas 763       return chain.getRootItem().getName();
4769 18 Apr 18 nicklas 764     }
4769 18 Apr 18 nicklas 765   }
5227 14 Jan 19 nicklas 766
4769 18 Apr 18 nicklas 767   /**
5227 14 Jan 19 nicklas 768     Get the name of the top item (typically the Patient).
5220 11 Jan 19 nicklas 769   */
5227 14 Jan 19 nicklas 770   static class TopItemNameColumn
5220 11 Jan 19 nicklas 771     extends ItemNameColumn
5220 11 Jan 19 nicklas 772   {
5227 14 Jan 19 nicklas 773     TopItemNameColumn(String header)
5220 11 Jan 19 nicklas 774     {
5220 11 Jan 19 nicklas 775       super(header);
5220 11 Jan 19 nicklas 776     }
5220 11 Jan 19 nicklas 777   
5220 11 Jan 19 nicklas 778     @Override
5227 14 Jan 19 nicklas 779     public Object getValue(CohortItem item, CohortChain chain, DbControl dc, SnapshotManager manager) 
5220 11 Jan 19 nicklas 780     {
5227 14 Jan 19 nicklas 781       return chain.getTopItem().getName();
5220 11 Jan 19 nicklas 782     }
5220 11 Jan 19 nicklas 783   }
5227 14 Jan 19 nicklas 784   
5220 11 Jan 19 nicklas 785   /**
5227 14 Jan 19 nicklas 786     Get the name of the parent item.
5220 11 Jan 19 nicklas 787   */
5227 14 Jan 19 nicklas 788   static class ParentItemNameColumn
5220 11 Jan 19 nicklas 789     extends ItemNameColumn
5220 11 Jan 19 nicklas 790   {
5227 14 Jan 19 nicklas 791     ParentItemNameColumn(String header)
5220 11 Jan 19 nicklas 792     {
5227 14 Jan 19 nicklas 793       super(header);
5220 11 Jan 19 nicklas 794     }
5220 11 Jan 19 nicklas 795   
5220 11 Jan 19 nicklas 796     @Override
5227 14 Jan 19 nicklas 797     public Object getValue(CohortItem item, CohortChain chain, DbControl dc, SnapshotManager manager) 
5220 11 Jan 19 nicklas 798     {
5227 14 Jan 19 nicklas 799       return item.getParentItem(dc).getName();
5220 11 Jan 19 nicklas 800     }
5220 11 Jan 19 nicklas 801   }
5220 11 Jan 19 nicklas 802
5220 11 Jan 19 nicklas 803   /**
4769 18 Apr 18 nicklas 804     Get value for a specified annotation type.
4769 18 Apr 18 nicklas 805   */
4769 18 Apr 18 nicklas 806   static class AnnotationColumn
4769 18 Apr 18 nicklas 807     implements ColumnWriter
4769 18 Apr 18 nicklas 808   {
4769 18 Apr 18 nicklas 809     private final AnnotationType at;
4769 18 Apr 18 nicklas 810     private final AnnotationTypeFilter filter;
4769 18 Apr 18 nicklas 811     
4769 18 Apr 18 nicklas 812     AnnotationColumn(AnnotationType at)
4769 18 Apr 18 nicklas 813     {
4769 18 Apr 18 nicklas 814       this.at = at;
4769 18 Apr 18 nicklas 815       this.filter = new AnnotationTypeFilter(at);
4769 18 Apr 18 nicklas 816     }
4769 18 Apr 18 nicklas 817
4769 18 Apr 18 nicklas 818     @Override
4769 18 Apr 18 nicklas 819     public String getHeader() 
4769 18 Apr 18 nicklas 820     {
4769 18 Apr 18 nicklas 821       return at.getName();
4769 18 Apr 18 nicklas 822     }
4769 18 Apr 18 nicklas 823
4769 18 Apr 18 nicklas 824     @Override
5227 14 Jan 19 nicklas 825     public Object getValue(CohortItem item, CohortChain chain, DbControl dc, SnapshotManager manager) 
4769 18 Apr 18 nicklas 826     {
4769 18 Apr 18 nicklas 827       List<AnnotationSnapshot> snapshots = manager.findAnnotations(dc, (Annotatable)item.getItem(), filter, false);
4769 18 Apr 18 nicklas 828       Object value = snapshots.size() > 0 ? snapshots.get(0).getThisValues().get(0) : null;
4769 18 Apr 18 nicklas 829       return value;
4769 18 Apr 18 nicklas 830     }
4769 18 Apr 18 nicklas 831     
4769 18 Apr 18 nicklas 832   }
4553 28 Aug 17 nicklas 833 }