extensions/net.sf.basedb.reggie/trunk/src/net/sf/basedb/reggie/plugins/ExternalRNAImporter.java

Code
Comments
Other
Rev Date Author Line
5630 27 Sep 19 nicklas 1 package net.sf.basedb.reggie.plugins;
5630 27 Sep 19 nicklas 2
5630 27 Sep 19 nicklas 3 import java.io.IOException;
5630 27 Sep 19 nicklas 4 import java.io.InputStream;
5630 27 Sep 19 nicklas 5 import java.util.ArrayList;
5630 27 Sep 19 nicklas 6 import java.util.HashMap;
5630 27 Sep 19 nicklas 7 import java.util.List;
5630 27 Sep 19 nicklas 8 import java.util.Map;
5630 27 Sep 19 nicklas 9 import java.util.TreeMap;
5630 27 Sep 19 nicklas 10 import java.util.regex.Pattern;
5630 27 Sep 19 nicklas 11
5630 27 Sep 19 nicklas 12 import org.json.simple.JSONArray;
5630 27 Sep 19 nicklas 13 import org.json.simple.JSONObject;
5630 27 Sep 19 nicklas 14
5630 27 Sep 19 nicklas 15 import net.sf.basedb.core.BioPlate;
5633 30 Sep 19 nicklas 16 import net.sf.basedb.core.BioWell;
5630 27 Sep 19 nicklas 17 import net.sf.basedb.core.DbControl;
5630 27 Sep 19 nicklas 18 import net.sf.basedb.core.Extract;
5630 27 Sep 19 nicklas 19 import net.sf.basedb.core.ItemQuery;
5632 30 Sep 19 nicklas 20 import net.sf.basedb.core.Type;
5630 27 Sep 19 nicklas 21 import net.sf.basedb.core.query.Expressions;
5630 27 Sep 19 nicklas 22 import net.sf.basedb.core.query.Hql;
5630 27 Sep 19 nicklas 23 import net.sf.basedb.core.query.Restrictions;
5630 27 Sep 19 nicklas 24 import net.sf.basedb.reggie.Reggie;
5632 30 Sep 19 nicklas 25 import net.sf.basedb.reggie.dao.Annotationtype;
5630 27 Sep 19 nicklas 26 import net.sf.basedb.reggie.dao.BioplateType;
5630 27 Sep 19 nicklas 27 import net.sf.basedb.reggie.dao.Subtype;
5630 27 Sep 19 nicklas 28 import net.sf.basedb.util.Coordinate;
5630 27 Sep 19 nicklas 29 import net.sf.basedb.util.Values;
5630 27 Sep 19 nicklas 30 import net.sf.basedb.util.excel.XlsxToCsvUtil;
5630 27 Sep 19 nicklas 31 import net.sf.basedb.util.parser.FlatFileParser;
5630 27 Sep 19 nicklas 32 import net.sf.basedb.util.parser.Mapper;
5630 27 Sep 19 nicklas 33 import net.sf.basedb.util.parser.FlatFileParser.LineType;
5630 27 Sep 19 nicklas 34
5630 27 Sep 19 nicklas 35 public class ExternalRNAImporter 
5630 27 Sep 19 nicklas 36 {
5630 27 Sep 19 nicklas 37   private final List<String> errorMessages;
5630 27 Sep 19 nicklas 38   private final List<String> warningMessages;
5630 27 Sep 19 nicklas 39
5630 27 Sep 19 nicklas 40   private final List<Aliquot> aliquots;
5630 27 Sep 19 nicklas 41   private final Map<String, Aliquot> aliquotsByName;
5630 27 Sep 19 nicklas 42   private final Map<String, Aliquot> aliquotsByLocation;
5630 27 Sep 19 nicklas 43   private final Map<String, AliquotPlate> platesByName;
5630 27 Sep 19 nicklas 44
5630 27 Sep 19 nicklas 45   private final Subtype aliquotType;
5630 27 Sep 19 nicklas 46   private final BioplateType plateType;
5630 27 Sep 19 nicklas 47   private final int MAX_COL;
5630 27 Sep 19 nicklas 48   private final int MAX_ROW;
5630 27 Sep 19 nicklas 49   
5630 27 Sep 19 nicklas 50   private int numImportedPlates;
5630 27 Sep 19 nicklas 51   private int numImportedAliquots;
5630 27 Sep 19 nicklas 52   
5630 27 Sep 19 nicklas 53   private String namePrefix;
5630 27 Sep 19 nicklas 54   private List<String> worksheets;
5630 27 Sep 19 nicklas 55   private String parsedWorksheet;
5630 27 Sep 19 nicklas 56   
5630 27 Sep 19 nicklas 57   public ExternalRNAImporter()
5630 27 Sep 19 nicklas 58   {
5630 27 Sep 19 nicklas 59     this.errorMessages = new ArrayList<String>();
5630 27 Sep 19 nicklas 60     this.warningMessages = new ArrayList<String>();
5630 27 Sep 19 nicklas 61     this.aliquots = new ArrayList<>();
5630 27 Sep 19 nicklas 62     this.aliquotsByName = new HashMap<>();
5630 27 Sep 19 nicklas 63     this.aliquotsByLocation = new HashMap<>();
5630 27 Sep 19 nicklas 64     this.platesByName = new TreeMap<>();
5630 27 Sep 19 nicklas 65     // TODO - implement as parameters?
5630 27 Sep 19 nicklas 66     this.aliquotType = Subtype.RNA;
5630 27 Sep 19 nicklas 67     this.plateType = BioplateType.STORAGE_BOX;
5630 27 Sep 19 nicklas 68     this.MAX_COL = 9;
5630 27 Sep 19 nicklas 69     this.MAX_ROW = 9;
5630 27 Sep 19 nicklas 70   }
5630 27 Sep 19 nicklas 71   
5630 27 Sep 19 nicklas 72   public void setNamePrefix(String prefix)
5630 27 Sep 19 nicklas 73   {
5630 27 Sep 19 nicklas 74     this.namePrefix = prefix;
5630 27 Sep 19 nicklas 75   }
5630 27 Sep 19 nicklas 76   
5630 27 Sep 19 nicklas 77   public boolean hasWarning()
5630 27 Sep 19 nicklas 78   {
5630 27 Sep 19 nicklas 79     return warningMessages.size() > 0;
5630 27 Sep 19 nicklas 80   }
5630 27 Sep 19 nicklas 81   
5630 27 Sep 19 nicklas 82   public List<String> getWarningMessages()
5630 27 Sep 19 nicklas 83   {
5630 27 Sep 19 nicklas 84     return warningMessages;
5630 27 Sep 19 nicklas 85   }
5630 27 Sep 19 nicklas 86   
5630 27 Sep 19 nicklas 87   public boolean hasError()
5630 27 Sep 19 nicklas 88   {
5630 27 Sep 19 nicklas 89     return errorMessages.size() > 0;
5630 27 Sep 19 nicklas 90   }
5630 27 Sep 19 nicklas 91   
5630 27 Sep 19 nicklas 92   public List<String> getErrorMessages()
5630 27 Sep 19 nicklas 93   {
5630 27 Sep 19 nicklas 94     return errorMessages;
5630 27 Sep 19 nicklas 95   }
5630 27 Sep 19 nicklas 96     
5630 27 Sep 19 nicklas 97   public void addWarningMessage(String msg)
5630 27 Sep 19 nicklas 98   {
5630 27 Sep 19 nicklas 99     this.warningMessages.add("[Warning] " + msg);
5630 27 Sep 19 nicklas 100   }
5630 27 Sep 19 nicklas 101   
5630 27 Sep 19 nicklas 102   public void addErrorMessage(String msg)
5630 27 Sep 19 nicklas 103   {
5630 27 Sep 19 nicklas 104     this.errorMessages.add("[Error] " + msg);
5630 27 Sep 19 nicklas 105   }
5630 27 Sep 19 nicklas 106   
5630 27 Sep 19 nicklas 107   /**
5630 27 Sep 19 nicklas 108     Get all plates ordered by name.
5630 27 Sep 19 nicklas 109   */
5630 27 Sep 19 nicklas 110   public List<AliquotPlate> getPlates()
5630 27 Sep 19 nicklas 111   {
5630 27 Sep 19 nicklas 112     return new ArrayList<>(platesByName.values());
5630 27 Sep 19 nicklas 113   }
5630 27 Sep 19 nicklas 114   
5630 27 Sep 19 nicklas 115   public int getNumImportedPlates()
5630 27 Sep 19 nicklas 116   {
5630 27 Sep 19 nicklas 117     return numImportedPlates;
5630 27 Sep 19 nicklas 118   }
5630 27 Sep 19 nicklas 119   
5630 27 Sep 19 nicklas 120   public int getNumImportedAliquots()
5630 27 Sep 19 nicklas 121   {
5630 27 Sep 19 nicklas 122     return numImportedAliquots;
5630 27 Sep 19 nicklas 123   }
5630 27 Sep 19 nicklas 124   
5630 27 Sep 19 nicklas 125   public int getNumAliquots()
5630 27 Sep 19 nicklas 126   {
5630 27 Sep 19 nicklas 127     return aliquots.size();
5630 27 Sep 19 nicklas 128   }
5630 27 Sep 19 nicklas 129   
5630 27 Sep 19 nicklas 130   public List<String> getWorksheets()
5630 27 Sep 19 nicklas 131   {
5630 27 Sep 19 nicklas 132     return worksheets;
5630 27 Sep 19 nicklas 133   }
5630 27 Sep 19 nicklas 134   
5630 27 Sep 19 nicklas 135   public String getParsedWorksheet()
5630 27 Sep 19 nicklas 136   {
5630 27 Sep 19 nicklas 137     return parsedWorksheet;
5630 27 Sep 19 nicklas 138   }
5630 27 Sep 19 nicklas 139   
5630 27 Sep 19 nicklas 140   /**
5630 27 Sep 19 nicklas 141     Import data from the input stream.
5630 27 Sep 19 nicklas 142     @param in
5630 27 Sep 19 nicklas 143   */
5630 27 Sep 19 nicklas 144   public boolean doImport(DbControl dc, InputStream in, String workSheet, boolean validateOnly, String fileName)
5630 27 Sep 19 nicklas 145     throws IOException
5630 27 Sep 19 nicklas 146   {
5630 27 Sep 19 nicklas 147     aliquots.clear();
5630 27 Sep 19 nicklas 148     aliquotsByName.clear();
5630 27 Sep 19 nicklas 149     aliquotsByLocation.clear();
5630 27 Sep 19 nicklas 150     platesByName.clear();
5630 27 Sep 19 nicklas 151     errorMessages.clear();
5630 27 Sep 19 nicklas 152     warningMessages.clear();
5630 27 Sep 19 nicklas 153     worksheets = null;
5630 27 Sep 19 nicklas 154     parsedWorksheet = null;
5630 27 Sep 19 nicklas 155     
5630 27 Sep 19 nicklas 156     FlatFileParser ffp = new FlatFileParser();
5630 27 Sep 19 nicklas 157     ffp.setExcelSheet(workSheet);
5630 27 Sep 19 nicklas 158     ffp.setDataHeaderRegexp(Pattern.compile(".*\\bName\\b.*"));
5630 27 Sep 19 nicklas 159     ffp.setDataSplitterRegexp(Pattern.compile("\t")); // Split on tab
5630 27 Sep 19 nicklas 160     ffp.setIgnoreRegexp(Pattern.compile("\\s*")); // Ignore lines with only white-space
5630 27 Sep 19 nicklas 161     ffp.setUseNullIfEmpty(true);
5630 27 Sep 19 nicklas 162     ffp.setTrimWhiteSpace(true);
5630 27 Sep 19 nicklas 163     
5630 27 Sep 19 nicklas 164     ffp.setInputStream(in, "UTF-8");
5630 27 Sep 19 nicklas 165     
5630 27 Sep 19 nicklas 166     XlsxToCsvUtil workbook = ffp.getCurrentExcelWorkbook();
5630 27 Sep 19 nicklas 167     if (workbook != null)
5630 27 Sep 19 nicklas 168     {
5630 27 Sep 19 nicklas 169       worksheets = workbook.getSheetNames();
5630 27 Sep 19 nicklas 170       parsedWorksheet = ffp.getExcelSheet();
5630 27 Sep 19 nicklas 171       fileName += "/" + parsedWorksheet;
5630 27 Sep 19 nicklas 172     }
5630 27 Sep 19 nicklas 173     
5630 27 Sep 19 nicklas 174     LineType headerLine = ffp.parseHeaders();
5630 27 Sep 19 nicklas 175     int lineNo = ffp.getParsedLines();
5630 27 Sep 19 nicklas 176     if (headerLine != LineType.DATA_HEADER)
5630 27 Sep 19 nicklas 177     {
5630 27 Sep 19 nicklas 178       addErrorMessage("File '" + fileName + "' line 1−" + lineNo + ": Could not find header line with 'Name' column.");
5630 27 Sep 19 nicklas 179       return false; // Can't continue if no data is found in the file
5630 27 Sep 19 nicklas 180     }
5630 27 Sep 19 nicklas 181     ffp.setIgnoreNonExistingColumns(true);
5630 27 Sep 19 nicklas 182     
5630 27 Sep 19 nicklas 183     String fileAndLine = "File '" + fileName + "' line " + lineNo + ": ";
5630 27 Sep 19 nicklas 184     Mapper nameMapper = getRequiredMapper(ffp, "Name", fileAndLine);
5630 27 Sep 19 nicklas 185     Mapper plateMapper = getRequiredMapper(ffp, "Plate", fileAndLine);
5630 27 Sep 19 nicklas 186     Mapper rowMapper = getRequiredMapper(ffp, "Row", fileAndLine);
5630 27 Sep 19 nicklas 187     Mapper columnMapper = getRequiredMapper(ffp, "Column", fileAndLine);
5630 27 Sep 19 nicklas 188     if (hasError()) return false;
5630 27 Sep 19 nicklas 189     
5630 27 Sep 19 nicklas 190     Mapper commentMapper = getOptionalMapper(ffp, "Comment", null);
5632 30 Sep 19 nicklas 191     Mapper quantityMapper = getOptionalMapper(ffp, "Quantity", fileAndLine);
5632 30 Sep 19 nicklas 192     Mapper ndConcMapper = getOptionalMapper(ffp, "NDConc", fileAndLine);
5632 30 Sep 19 nicklas 193     Mapper nd260by230Mapper = getOptionalMapper(ffp, "ND260by230", fileAndLine);
5632 30 Sep 19 nicklas 194     Mapper nd260by280Mapper = getOptionalMapper(ffp, "ND260by280", fileAndLine);
5632 30 Sep 19 nicklas 195     Mapper rinMapper = getOptionalMapper(ffp, "RIN", fileAndLine);
5632 30 Sep 19 nicklas 196     Mapper rqsMapper = getOptionalMapper(ffp, "RQS", fileAndLine);
5632 30 Sep 19 nicklas 197   
5630 27 Sep 19 nicklas 198     while (ffp.hasMoreData())
5630 27 Sep 19 nicklas 199     {
5630 27 Sep 19 nicklas 200       FlatFileParser.Data data = ffp.nextData();
5630 27 Sep 19 nicklas 201       lineNo = ffp.getParsedLines();
5630 27 Sep 19 nicklas 202       fileAndLine = "File '" + fileName + "' line " + lineNo + ": ";
5630 27 Sep 19 nicklas 203
5630 27 Sep 19 nicklas 204       String name = nameMapper.getString(data);
5630 27 Sep 19 nicklas 205       if (name == null) continue; // Skip lines with no name
5630 27 Sep 19 nicklas 206       
5630 27 Sep 19 nicklas 207       Aliquot a = new Aliquot(fileAndLine);
5630 27 Sep 19 nicklas 208       aliquots.add(a);
5630 27 Sep 19 nicklas 209       a.lineNo = lineNo;
5630 27 Sep 19 nicklas 210       a.name = mergePrefix(namePrefix, name);
5630 27 Sep 19 nicklas 211       a.prefix = namePrefix;
5630 27 Sep 19 nicklas 212       a.plateName = plateMapper.getString(data);
5630 27 Sep 19 nicklas 213       a.row = rowMapper.getString(data);
5630 27 Sep 19 nicklas 214       a.column = columnMapper.getString(data);
5632 30 Sep 19 nicklas 215       if (commentMapper != null) a.comment = commentMapper.getString(data);
5636 02 Oct 19 nicklas 216       if (quantityMapper != null) 
5636 02 Oct 19 nicklas 217       {
5636 02 Oct 19 nicklas 218         a.quantity = quantityMapper.getString(data);
5636 02 Oct 19 nicklas 219         if (a.quantity == null)
5636 02 Oct 19 nicklas 220         {
5636 02 Oct 19 nicklas 221           if (a.warning == null) a.warning = "Missing quantity";
5636 02 Oct 19 nicklas 222           addWarningMessage(fileAndLine+"Missing quantity for '"+a.name+"'");
5636 02 Oct 19 nicklas 223         }
5636 02 Oct 19 nicklas 224       }
5636 02 Oct 19 nicklas 225       if (ndConcMapper != null) 
5636 02 Oct 19 nicklas 226       {
5636 02 Oct 19 nicklas 227         a.ndConc = ndConcMapper.getString(data);
5636 02 Oct 19 nicklas 228         if (a.ndConc == null)
5636 02 Oct 19 nicklas 229         {
5636 02 Oct 19 nicklas 230           if (a.warning == null) a.warning = "Missing NDConc";
5636 02 Oct 19 nicklas 231           addWarningMessage(fileAndLine+"Missing NDConc for '"+a.name+"'");
5636 02 Oct 19 nicklas 232         }
5636 02 Oct 19 nicklas 233       }
5632 30 Sep 19 nicklas 234       if (nd260by230Mapper != null) a.nd260by230 = nd260by230Mapper.getString(data);
5632 30 Sep 19 nicklas 235       if (nd260by280Mapper != null) a.nd260by280 = nd260by280Mapper.getString(data);
5632 30 Sep 19 nicklas 236       if (rinMapper != null) a.baRIN = rinMapper.getString(data);
5632 30 Sep 19 nicklas 237       if (rqsMapper != null) a.caRQS = rqsMapper.getString(data);
5632 30 Sep 19 nicklas 238
5630 27 Sep 19 nicklas 239       boolean valid = checkAliquot(dc, a);
5630 27 Sep 19 nicklas 240
5630 27 Sep 19 nicklas 241       AliquotPlate plate = null;
5630 27 Sep 19 nicklas 242       if (a.plateName != null)
5630 27 Sep 19 nicklas 243       {
5630 27 Sep 19 nicklas 244         plate = platesByName.get(a.plateName);
5630 27 Sep 19 nicklas 245         if (plate == null)
5630 27 Sep 19 nicklas 246         {
5630 27 Sep 19 nicklas 247           plate = new AliquotPlate();
5630 27 Sep 19 nicklas 248           plate.name = a.plateName;
5630 27 Sep 19 nicklas 249           platesByName.put(plate.name, plate);
5630 27 Sep 19 nicklas 250           boolean plateValid = checkPlate(dc, plate, fileAndLine);
5630 27 Sep 19 nicklas 251
5630 27 Sep 19 nicklas 252           if (!validateOnly && plateValid && plate.aliquotPlate == null)
5630 27 Sep 19 nicklas 253           {
5630 27 Sep 19 nicklas 254             plate.aliquotPlate = BioPlate.getNew(dc, plateType.getPlateGeometry(dc), plateType.load(dc));
5630 27 Sep 19 nicklas 255             plate.aliquotPlate.setName(plate.name);
5630 27 Sep 19 nicklas 256             dc.saveItem(plate.aliquotPlate);
5630 27 Sep 19 nicklas 257             numImportedPlates++;
5630 27 Sep 19 nicklas 258           }
5630 27 Sep 19 nicklas 259         }
5630 27 Sep 19 nicklas 260
5630 27 Sep 19 nicklas 261         if (valid && plate.aliquotPlate != null)
5630 27 Sep 19 nicklas 262         {
5630 27 Sep 19 nicklas 263           valid = checkExistingAliquotOnExistingPlate(dc, plate, a, fileAndLine);
5630 27 Sep 19 nicklas 264         }
5630 27 Sep 19 nicklas 265
5630 27 Sep 19 nicklas 266         plate.addAliquot(a);
5630 27 Sep 19 nicklas 267       }
5630 27 Sep 19 nicklas 268       
5630 27 Sep 19 nicklas 269       if (!validateOnly && valid && plate.aliquotPlate != null)
5630 27 Sep 19 nicklas 270       {
5630 27 Sep 19 nicklas 271         Extract aliquot = Extract.getNew(dc);
5630 27 Sep 19 nicklas 272         aliquot.setName(a.name);
5630 27 Sep 19 nicklas 273         aliquot.setItemSubtype(aliquotType.get(dc));
5630 27 Sep 19 nicklas 274         aliquot.setDescription(a.comment);
5630 27 Sep 19 nicklas 275         aliquot.getCreationEvent().setProtocol(null); // Otherwise, BASE will auto-set it to the project default
5630 27 Sep 19 nicklas 276         int row = Coordinate.alphaToNumeric(a.row)-1;
5630 27 Sep 19 nicklas 277         int column = Values.getInt(a.column)-1;
5630 27 Sep 19 nicklas 278         aliquot.setBioWell(plate.aliquotPlate.getBioWell(row, column));
5632 30 Sep 19 nicklas 279         if (a.quantity != null) aliquot.setOriginalQuantity(quantityMapper.getFloat(data));
5632 30 Sep 19 nicklas 280         if (a.ndConc != null) Annotationtype.ND_CONC.setAnnotationValue(dc, aliquot, ndConcMapper.getFloat(data));
5632 30 Sep 19 nicklas 281         if (a.nd260by230 != null) Annotationtype.ND_260_BY_230.setAnnotationValue(dc, aliquot, nd260by230Mapper.getFloat(data));
5632 30 Sep 19 nicklas 282         if (a.nd260by280 != null) Annotationtype.ND_260_BY_280.setAnnotationValue(dc, aliquot, nd260by280Mapper.getFloat(data));
5632 30 Sep 19 nicklas 283         if (a.baRIN != null) Annotationtype.BA_RIN.setAnnotationValue(dc, aliquot, rinMapper.getFloat(data));
5632 30 Sep 19 nicklas 284         if (a.caRQS != null) Annotationtype.CA_RQS.setAnnotationValue(dc, aliquot, rqsMapper.getFloat(data));
5632 30 Sep 19 nicklas 285
5630 27 Sep 19 nicklas 286         dc.saveItem(aliquot);
5630 27 Sep 19 nicklas 287         numImportedAliquots++;
5630 27 Sep 19 nicklas 288       }
5630 27 Sep 19 nicklas 289     }
5630 27 Sep 19 nicklas 290     
5630 27 Sep 19 nicklas 291     if (aliquots.isEmpty()) 
5630 27 Sep 19 nicklas 292     {
5630 27 Sep 19 nicklas 293       addErrorMessage("File '" + fileName + "' line 1−" + lineNo + ": No aliquots.");
5630 27 Sep 19 nicklas 294     }
5630 27 Sep 19 nicklas 295     
5630 27 Sep 19 nicklas 296     return !hasError();
5630 27 Sep 19 nicklas 297   }
5630 27 Sep 19 nicklas 298   
5630 27 Sep 19 nicklas 299   /**
5630 27 Sep 19 nicklas 300     Get a mapper for a required column. If the column doesn't exists
5630 27 Sep 19 nicklas 301     an error message is logged and null is returned.
5630 27 Sep 19 nicklas 302   */
5630 27 Sep 19 nicklas 303   private Mapper getRequiredMapper(FlatFileParser ffp, String col, String fileAndLine)
5630 27 Sep 19 nicklas 304   {
5630 27 Sep 19 nicklas 305     Mapper mapper = null;
5630 27 Sep 19 nicklas 306     if (ffp.getColumnHeaderIndex(col) == null)
5630 27 Sep 19 nicklas 307     {
5630 27 Sep 19 nicklas 308       addErrorMessage(fileAndLine+"Column '" + col + "' not found in column headers.");
5630 27 Sep 19 nicklas 309     }
5630 27 Sep 19 nicklas 310     else
5630 27 Sep 19 nicklas 311     {
5630 27 Sep 19 nicklas 312       mapper = ffp.getMapper("\\" + col + "\\");
5630 27 Sep 19 nicklas 313     }
5630 27 Sep 19 nicklas 314     return mapper;
5630 27 Sep 19 nicklas 315   }
5630 27 Sep 19 nicklas 316   
5630 27 Sep 19 nicklas 317   /**
5630 27 Sep 19 nicklas 318     Get a mapper for an optional column. If the column doesn't exists
5630 27 Sep 19 nicklas 319     a warning message is logged (unless 'fileAndLine' is null) 
5630 27 Sep 19 nicklas 320     and null is returned.
5630 27 Sep 19 nicklas 321   */
5630 27 Sep 19 nicklas 322   private Mapper getOptionalMapper(FlatFileParser ffp, String col, String fileAndLine)
5630 27 Sep 19 nicklas 323   {
5630 27 Sep 19 nicklas 324     Mapper mapper = null;
5630 27 Sep 19 nicklas 325     if (ffp.getColumnHeaderIndex(col) == null)
5630 27 Sep 19 nicklas 326     {
5630 27 Sep 19 nicklas 327       if (fileAndLine != null)
5630 27 Sep 19 nicklas 328       {
5630 27 Sep 19 nicklas 329         addWarningMessage(fileAndLine+"Column '" + col + "' not found in column headers.");
5630 27 Sep 19 nicklas 330       }
5630 27 Sep 19 nicklas 331     }
5630 27 Sep 19 nicklas 332     else
5630 27 Sep 19 nicklas 333     {
5630 27 Sep 19 nicklas 334       mapper = ffp.getMapper("\\" + col + "\\");
5630 27 Sep 19 nicklas 335     }
5630 27 Sep 19 nicklas 336     return mapper;
5630 27 Sep 19 nicklas 337   }
5630 27 Sep 19 nicklas 338   
5630 27 Sep 19 nicklas 339   /**
5630 27 Sep 19 nicklas 340     Merge the prefix and the name. If name is null, null is returned.
5630 27 Sep 19 nicklas 341     If the name already starts with the prefix it is returned as it is.
5630 27 Sep 19 nicklas 342     Otherwise the prefix is added to the name.
5630 27 Sep 19 nicklas 343   */
5630 27 Sep 19 nicklas 344   private String mergePrefix(String prefix, String name)
5630 27 Sep 19 nicklas 345   {
5630 27 Sep 19 nicklas 346     if (name == null || name.startsWith(prefix)) return name;
5630 27 Sep 19 nicklas 347     return prefix+name;
5630 27 Sep 19 nicklas 348   }
5630 27 Sep 19 nicklas 349   
5630 27 Sep 19 nicklas 350   /**
5630 27 Sep 19 nicklas 351     Check that the aliquot information is good.
5630 27 Sep 19 nicklas 352     * A name is required
5630 27 Sep 19 nicklas 353     * The name must be unique within the file
5630 27 Sep 19 nicklas 354     * An aliquot with the same name must not exist in the database
5630 27 Sep 19 nicklas 355     * A plate name is required
5630 27 Sep 19 nicklas 356     * The location on the plate must be within A1 to H12
5630 27 Sep 19 nicklas 357     * The same location must not be used by more than one aliquot
5630 27 Sep 19 nicklas 358   */
5630 27 Sep 19 nicklas 359   public boolean checkAliquot(DbControl dc, Aliquot a)
5630 27 Sep 19 nicklas 360   {
5630 27 Sep 19 nicklas 361     if (a.name == null)
5630 27 Sep 19 nicklas 362     {
5630 27 Sep 19 nicklas 363       a.error = "Missing name!";
5630 27 Sep 19 nicklas 364       addErrorMessage(a.fileAndLine+"Missing aliquot name");
5630 27 Sep 19 nicklas 365       return false;
5630 27 Sep 19 nicklas 366     }
5630 27 Sep 19 nicklas 367
5630 27 Sep 19 nicklas 368     Aliquot other = aliquotsByName.get(a.name);
5630 27 Sep 19 nicklas 369     if (other != null)
5630 27 Sep 19 nicklas 370     {
5630 27 Sep 19 nicklas 371       a.error = "Duplicate name!";
5630 27 Sep 19 nicklas 372       if (other.error == null) other.error = "Duplicate name";
5630 27 Sep 19 nicklas 373       addErrorMessage(a.fileAndLine+"Duplicate name: " + a.name + " (also found on line " + other.lineNo + ")");
5630 27 Sep 19 nicklas 374       return false;
5630 27 Sep 19 nicklas 375     }
5630 27 Sep 19 nicklas 376     aliquotsByName.put(a.name, a);
5630 27 Sep 19 nicklas 377     
5630 27 Sep 19 nicklas 378     ItemQuery<Extract> query = Extract.getQuery();
5630 27 Sep 19 nicklas 379     query.setIncludes(Reggie.INCLUDE_IN_CURRENT_PROJECT);
5630 27 Sep 19 nicklas 380     query.restrict(Restrictions.eq(Hql.property("name"), Expressions.string(a.name)));
5630 27 Sep 19 nicklas 381     if (query.count(dc) > 0)
5630 27 Sep 19 nicklas 382     {
5630 27 Sep 19 nicklas 383       a.error = "Already exists!";
5630 27 Sep 19 nicklas 384       addErrorMessage(a.fileAndLine+"Already exists: " + a.name);
5630 27 Sep 19 nicklas 385       return false;
5630 27 Sep 19 nicklas 386     }
5630 27 Sep 19 nicklas 387     
5630 27 Sep 19 nicklas 388     if (a.plateName== null)
5630 27 Sep 19 nicklas 389     {
5630 27 Sep 19 nicklas 390       a.error = "No plate name!";
5630 27 Sep 19 nicklas 391       addErrorMessage(a.fileAndLine+"Missing plate name for '" + a.name + "'");
5630 27 Sep 19 nicklas 392       return false;
5630 27 Sep 19 nicklas 393     }
5630 27 Sep 19 nicklas 394     
5630 27 Sep 19 nicklas 395     String location = a.plateName+"["+Values.getString(a.row)+Values.getString(a.column)+"]";
5630 27 Sep 19 nicklas 396     if (a.row == null || a.column == null)
5630 27 Sep 19 nicklas 397     {
5630 27 Sep 19 nicklas 398       a.error = "No location!";
5630 27 Sep 19 nicklas 399       addErrorMessage(a.fileAndLine+"Missing location for '" + a.name + "': " + location);
5630 27 Sep 19 nicklas 400       return false;
5630 27 Sep 19 nicklas 401     }
5630 27 Sep 19 nicklas 402     try
5630 27 Sep 19 nicklas 403     {
5630 27 Sep 19 nicklas 404       int row = Coordinate.alphaToNumeric(a.row);
5630 27 Sep 19 nicklas 405       int col = Values.getInt(a.column);
5630 27 Sep 19 nicklas 406       if (row < 1 || row > MAX_ROW || col < 1 || col > MAX_COL)
5630 27 Sep 19 nicklas 407       {
5630 27 Sep 19 nicklas 408         throw new RuntimeException();
5630 27 Sep 19 nicklas 409       }
5630 27 Sep 19 nicklas 410     }
5630 27 Sep 19 nicklas 411     catch (RuntimeException ex)
5630 27 Sep 19 nicklas 412     {
5630 27 Sep 19 nicklas 413       a.error = "Location!";
5630 27 Sep 19 nicklas 414       addErrorMessage(a.fileAndLine+"Invalid location for '" + a.name + "': " + location);
5630 27 Sep 19 nicklas 415       return false;
5630 27 Sep 19 nicklas 416     }
5630 27 Sep 19 nicklas 417     
5630 27 Sep 19 nicklas 418     other = aliquotsByLocation.get(location);
5630 27 Sep 19 nicklas 419     if (other != null)
5630 27 Sep 19 nicklas 420     {
5630 27 Sep 19 nicklas 421       a.error = "Multiple aliquots!";
5630 27 Sep 19 nicklas 422       if (other.error == null) other.error = "Multiple aliquots";
5630 27 Sep 19 nicklas 423       addErrorMessage(a.fileAndLine+"Multiple aliquots on location '" + location + "': " + a.name + " and " + other.name + " (line " + other.lineNo + ")");
5630 27 Sep 19 nicklas 424       return false;
5630 27 Sep 19 nicklas 425     }
5630 27 Sep 19 nicklas 426     aliquotsByLocation.put(location, a);
5630 27 Sep 19 nicklas 427     
5632 30 Sep 19 nicklas 428     if (!checkFloat(a.quantity))
5632 30 Sep 19 nicklas 429     {
5632 30 Sep 19 nicklas 430       a.error = "Quantity!";
5632 30 Sep 19 nicklas 431       addErrorMessage(a.fileAndLine+"Invalid Quantity for '" + a.name + "': " + a.quantity);
5632 30 Sep 19 nicklas 432       return false;
5632 30 Sep 19 nicklas 433     }
5632 30 Sep 19 nicklas 434     if (a.quantity != null) a.quantityF = (Float)Type.FLOAT.parseString(a.quantity);
5632 30 Sep 19 nicklas 435     if (!checkFloat(a.ndConc))
5632 30 Sep 19 nicklas 436     {
5632 30 Sep 19 nicklas 437       a.error = "NDConc!";
5632 30 Sep 19 nicklas 438       addErrorMessage(a.fileAndLine+"Invalid NDConc for '" + a.name + "': " + a.ndConc);
5632 30 Sep 19 nicklas 439       return false;
5632 30 Sep 19 nicklas 440     }
5632 30 Sep 19 nicklas 441     if (a.ndConc != null) a.ndConcF = (Float)Type.FLOAT.parseString(a.ndConc);
5632 30 Sep 19 nicklas 442     if (a.quantityF != null && a.ndConcF != null)
5632 30 Sep 19 nicklas 443     {
5632 30 Sep 19 nicklas 444       a.volume = a.quantityF * 1000 / a.ndConcF;
5632 30 Sep 19 nicklas 445     }
5632 30 Sep 19 nicklas 446     if (!checkFloat(a.nd260by230))
5632 30 Sep 19 nicklas 447     {
5632 30 Sep 19 nicklas 448       a.error = "ND260by230!";
5632 30 Sep 19 nicklas 449       addErrorMessage(a.fileAndLine+"Invalid ND260by230 for '" + a.name + "': " + a.nd260by230);
5632 30 Sep 19 nicklas 450       return false;
5632 30 Sep 19 nicklas 451     }
5632 30 Sep 19 nicklas 452     if (!checkFloat(a.nd260by280))
5632 30 Sep 19 nicklas 453     {
5632 30 Sep 19 nicklas 454       a.error = "ND260by280!";
5632 30 Sep 19 nicklas 455       addErrorMessage(a.fileAndLine+"Invalid ND260by280 for '" + a.name + "': " + a.nd260by280);
5632 30 Sep 19 nicklas 456       return false;
5632 30 Sep 19 nicklas 457     }
5632 30 Sep 19 nicklas 458     if (!checkFloat(a.baRIN))
5632 30 Sep 19 nicklas 459     {
5632 30 Sep 19 nicklas 460       a.error = "RIN!";
5632 30 Sep 19 nicklas 461       addErrorMessage(a.fileAndLine+"Invalid RIN for '" + a.name + "': " + a.baRIN);
5632 30 Sep 19 nicklas 462       return false;
5632 30 Sep 19 nicklas 463     }
5632 30 Sep 19 nicklas 464     if (!checkFloat(a.caRQS))
5632 30 Sep 19 nicklas 465     {
5632 30 Sep 19 nicklas 466       a.error = "RQS!";
5632 30 Sep 19 nicklas 467       addErrorMessage(a.fileAndLine+"Invalid RQS for '" + a.name + "': " + a.caRQS);
5632 30 Sep 19 nicklas 468       return false;
5632 30 Sep 19 nicklas 469     }
5632 30 Sep 19 nicklas 470     
5630 27 Sep 19 nicklas 471     return true;
5630 27 Sep 19 nicklas 472   }
5630 27 Sep 19 nicklas 473   
5632 30 Sep 19 nicklas 474   private boolean checkFloat(String value)
5632 30 Sep 19 nicklas 475   {
5632 30 Sep 19 nicklas 476     if (value != null)
5632 30 Sep 19 nicklas 477     {
5632 30 Sep 19 nicklas 478       try
5632 30 Sep 19 nicklas 479       {
5632 30 Sep 19 nicklas 480         Type.FLOAT.parseString(value);
5632 30 Sep 19 nicklas 481       }
5632 30 Sep 19 nicklas 482       catch (RuntimeException ex)
5632 30 Sep 19 nicklas 483       {
5632 30 Sep 19 nicklas 484         return false;
5632 30 Sep 19 nicklas 485       }
5632 30 Sep 19 nicklas 486     }
5632 30 Sep 19 nicklas 487     return true;
5632 30 Sep 19 nicklas 488
5632 30 Sep 19 nicklas 489   }
5632 30 Sep 19 nicklas 490   
5630 27 Sep 19 nicklas 491   /**
5630 27 Sep 19 nicklas 492     Check that the plate information is good.
5630 27 Sep 19 nicklas 493     * There can be at most one plate with the same name in the database
5630 27 Sep 19 nicklas 494   */
5630 27 Sep 19 nicklas 495   public boolean checkPlate(DbControl dc, AliquotPlate plate, String fileAndLine)
5630 27 Sep 19 nicklas 496   {
5630 27 Sep 19 nicklas 497     ItemQuery<BioPlate> query = BioPlate.getQuery();
5630 27 Sep 19 nicklas 498     query.setIncludes(Reggie.INCLUDE_IN_CURRENT_PROJECT);
5630 27 Sep 19 nicklas 499     query.restrict(Restrictions.eq(Hql.property("name"), Expressions.string(plate.name)));
5630 27 Sep 19 nicklas 500     long numPlates = query.count(dc);
5630 27 Sep 19 nicklas 501     if (numPlates > 1)
5630 27 Sep 19 nicklas 502     {
5630 27 Sep 19 nicklas 503       plate.error = numPlates + " plates already exists";
5630 27 Sep 19 nicklas 504       addErrorMessage(fileAndLine+numPlates+" plates already exists: " + plate.name);
5630 27 Sep 19 nicklas 505       return false;
5630 27 Sep 19 nicklas 506     }
5630 27 Sep 19 nicklas 507     else if (numPlates == 1)
5630 27 Sep 19 nicklas 508     {
5630 27 Sep 19 nicklas 509       plate.aliquotPlate = query.list(dc).get(0);
5633 30 Sep 19 nicklas 510       
5633 30 Sep 19 nicklas 511       ItemQuery<Extract> existingQuery = Extract.getQuery();
5633 30 Sep 19 nicklas 512       existingQuery.setIncludes(Reggie.INCLUDE_IN_CURRENT_PROJECT);
5633 30 Sep 19 nicklas 513       existingQuery.join(Hql.innerJoin("bioWell.bioPlate", "bp"));
5633 30 Sep 19 nicklas 514       existingQuery.restrict(Restrictions.eq(Hql.alias("bp"), Hql.entity(plate.aliquotPlate)));
5633 30 Sep 19 nicklas 515       for (Extract e : existingQuery.list(dc))
5633 30 Sep 19 nicklas 516       {
5633 30 Sep 19 nicklas 517         BioWell well = e.getBioWell();
5633 30 Sep 19 nicklas 518         Aliquot a = new Aliquot(null);
5633 30 Sep 19 nicklas 519         a.id = e.getId();
5633 30 Sep 19 nicklas 520         a.name = e.getName();
5633 30 Sep 19 nicklas 521         a.row = Coordinate.numericToAlpha(well.getRow() + 1);
5633 30 Sep 19 nicklas 522         a.column = Integer.toString(well.getColumn() + 1);
5633 30 Sep 19 nicklas 523         plate.addExistingAliquot(a);
5633 30 Sep 19 nicklas 524       }
5633 30 Sep 19 nicklas 525       
5630 27 Sep 19 nicklas 526     }
5630 27 Sep 19 nicklas 527     return true;
5630 27 Sep 19 nicklas 528   }
5630 27 Sep 19 nicklas 529   
5630 27 Sep 19 nicklas 530   /**
5630 27 Sep 19 nicklas 531     If the aliquot plate already exists, check that the location specified by the aliquot
5630 27 Sep 19 nicklas 532     is not already taken by another existing item. Note that this will only check what is
5630 27 Sep 19 nicklas 533     already stored in the database. Duplicate locations from the file are checked by
5630 27 Sep 19 nicklas 534     {@link #checkAliquot(DbControl, Aliquot)}.
5630 27 Sep 19 nicklas 535   */
5630 27 Sep 19 nicklas 536   public boolean checkExistingAliquotOnExistingPlate(DbControl dc, AliquotPlate plate, Aliquot a, String fileAndLine)
5630 27 Sep 19 nicklas 537   {
5630 27 Sep 19 nicklas 538     if (plate.aliquotPlate == null || !plate.aliquotPlate.isInDatabase()) return true;
5630 27 Sep 19 nicklas 539     
5633 30 Sep 19 nicklas 540     Aliquot other = plate.getExistingOnSameLocation(a);
5630 27 Sep 19 nicklas 541     if (other != null)
5630 27 Sep 19 nicklas 542     {
5630 27 Sep 19 nicklas 543       a.error = "Location in use!";
5630 27 Sep 19 nicklas 544       String location = a.plateName+"["+Values.getString(a.row)+Values.getString(a.column)+"]";
5633 30 Sep 19 nicklas 545       addErrorMessage(a.fileAndLine+"Location '" + location + "' is already used by another sample: " + other.name);
5630 27 Sep 19 nicklas 546       return false;
5630 27 Sep 19 nicklas 547     }
5630 27 Sep 19 nicklas 548     return true;
5630 27 Sep 19 nicklas 549   }
5630 27 Sep 19 nicklas 550   
5630 27 Sep 19 nicklas 551   public static class AliquotPlate
5630 27 Sep 19 nicklas 552   {
5630 27 Sep 19 nicklas 553     String name;
5630 27 Sep 19 nicklas 554     String error;
5630 27 Sep 19 nicklas 555     BioPlate aliquotPlate;
5630 27 Sep 19 nicklas 556     private final List<Aliquot> aliquots;
5633 30 Sep 19 nicklas 557     private final Map<String, Aliquot> existingAliquots;
5630 27 Sep 19 nicklas 558     
5630 27 Sep 19 nicklas 559     public AliquotPlate()
5630 27 Sep 19 nicklas 560     {
5630 27 Sep 19 nicklas 561       this.aliquots = new ArrayList<>();
5633 30 Sep 19 nicklas 562       this.existingAliquots = new TreeMap<>();
5630 27 Sep 19 nicklas 563     }
5630 27 Sep 19 nicklas 564
5630 27 Sep 19 nicklas 565     public String getName()
5630 27 Sep 19 nicklas 566     {
5630 27 Sep 19 nicklas 567       return name;
5630 27 Sep 19 nicklas 568     }
5630 27 Sep 19 nicklas 569     
5630 27 Sep 19 nicklas 570     public int getNumAliquots()
5630 27 Sep 19 nicklas 571     {
5630 27 Sep 19 nicklas 572       return aliquots.size();
5630 27 Sep 19 nicklas 573     }
5630 27 Sep 19 nicklas 574     
5635 01 Oct 19 nicklas 575     public BioPlate getBioPlate()
5635 01 Oct 19 nicklas 576     {
5635 01 Oct 19 nicklas 577       return aliquotPlate;
5635 01 Oct 19 nicklas 578     }
5635 01 Oct 19 nicklas 579     
5630 27 Sep 19 nicklas 580     public void addAliquot(Aliquot a)
5630 27 Sep 19 nicklas 581     {
5630 27 Sep 19 nicklas 582       aliquots.add(a);
5630 27 Sep 19 nicklas 583     }
5630 27 Sep 19 nicklas 584     
5633 30 Sep 19 nicklas 585     public void addExistingAliquot(Aliquot a)
5633 30 Sep 19 nicklas 586     {
5633 30 Sep 19 nicklas 587       existingAliquots.put(a.row+a.column, a);
5633 30 Sep 19 nicklas 588     }
5633 30 Sep 19 nicklas 589     
5633 30 Sep 19 nicklas 590     public Aliquot getExistingOnSameLocation(Aliquot a)
5633 30 Sep 19 nicklas 591     {
5633 30 Sep 19 nicklas 592       return existingAliquots.get(a.row+a.column);
5633 30 Sep 19 nicklas 593     }
5633 30 Sep 19 nicklas 594     
5630 27 Sep 19 nicklas 595     public JSONObject toJSONObject()
5630 27 Sep 19 nicklas 596     {
5630 27 Sep 19 nicklas 597       JSONObject json = new JSONObject();
5630 27 Sep 19 nicklas 598       json.put("name", name);
5635 01 Oct 19 nicklas 599       json.put("id", aliquotPlate != null ? aliquotPlate.getId() : 0);
5630 27 Sep 19 nicklas 600       json.put("error", error);
5630 27 Sep 19 nicklas 601       JSONArray jsonAliquots = new JSONArray();
5630 27 Sep 19 nicklas 602       for (Aliquot a :  aliquots)
5630 27 Sep 19 nicklas 603       {
5630 27 Sep 19 nicklas 604         jsonAliquots.add(a.toJSONObject());
5630 27 Sep 19 nicklas 605       }
5630 27 Sep 19 nicklas 606       json.put("aliquots", jsonAliquots);
5633 30 Sep 19 nicklas 607       JSONArray jsonExistingAliquots = new JSONArray();
5633 30 Sep 19 nicklas 608       for (Aliquot a :  existingAliquots.values())
5633 30 Sep 19 nicklas 609       {
5633 30 Sep 19 nicklas 610         jsonExistingAliquots.add(a.toJSONObject());
5633 30 Sep 19 nicklas 611       }
5633 30 Sep 19 nicklas 612       json.put("existingAliquots", jsonExistingAliquots);
5630 27 Sep 19 nicklas 613       return json;
5630 27 Sep 19 nicklas 614     }
5630 27 Sep 19 nicklas 615   }
5630 27 Sep 19 nicklas 616   
5630 27 Sep 19 nicklas 617   public static class Aliquot
5630 27 Sep 19 nicklas 618   {
5630 27 Sep 19 nicklas 619     final String fileAndLine;
5630 27 Sep 19 nicklas 620     int lineNo;
5633 30 Sep 19 nicklas 621     int id;
5630 27 Sep 19 nicklas 622     String name;
5630 27 Sep 19 nicklas 623     String prefix;
5630 27 Sep 19 nicklas 624     String plateName;
5630 27 Sep 19 nicklas 625     String row;
5630 27 Sep 19 nicklas 626     String column;
5630 27 Sep 19 nicklas 627     String comment;
5632 30 Sep 19 nicklas 628     String quantity;
5632 30 Sep 19 nicklas 629     Float quantityF;
5632 30 Sep 19 nicklas 630     String ndConc;
5632 30 Sep 19 nicklas 631     Float ndConcF;
5632 30 Sep 19 nicklas 632     Float volume;
5632 30 Sep 19 nicklas 633     String nd260by280;
5632 30 Sep 19 nicklas 634     String nd260by230;
5632 30 Sep 19 nicklas 635     String baRIN;
5632 30 Sep 19 nicklas 636     String caRQS;
5630 27 Sep 19 nicklas 637     String error;
5630 27 Sep 19 nicklas 638     String warning;
5630 27 Sep 19 nicklas 639     
5630 27 Sep 19 nicklas 640     public Aliquot(String fileAndLine)
5630 27 Sep 19 nicklas 641     {
5630 27 Sep 19 nicklas 642       this.fileAndLine = fileAndLine;
5630 27 Sep 19 nicklas 643     }
5630 27 Sep 19 nicklas 644     
5630 27 Sep 19 nicklas 645     public JSONObject toJSONObject()
5630 27 Sep 19 nicklas 646     {
5630 27 Sep 19 nicklas 647       JSONObject json = new JSONObject();
5630 27 Sep 19 nicklas 648       json.put("name", name);
5633 30 Sep 19 nicklas 649       json.put("id", id);
5630 27 Sep 19 nicklas 650       json.put("prefix", prefix);
5630 27 Sep 19 nicklas 651       json.put("plateName", plateName);
5630 27 Sep 19 nicklas 652       json.put("row", row);
5630 27 Sep 19 nicklas 653       json.put("column", column);
5630 27 Sep 19 nicklas 654       json.put("location", row+column);
5630 27 Sep 19 nicklas 655       json.put("comment", comment);
5632 30 Sep 19 nicklas 656       json.put("quantity", quantity);
5632 30 Sep 19 nicklas 657       json.put("quantityF", quantityF);
5632 30 Sep 19 nicklas 658       json.put("NDConc", ndConc);
5632 30 Sep 19 nicklas 659       json.put("NDConcF", ndConcF);
5632 30 Sep 19 nicklas 660       json.put("volume", volume);
5632 30 Sep 19 nicklas 661       json.put("ND260by280", nd260by280);
5632 30 Sep 19 nicklas 662       json.put("ND260by230", nd260by230);
5632 30 Sep 19 nicklas 663       json.put("BA_RIN", baRIN);
5632 30 Sep 19 nicklas 664       json.put("CA_RQS", caRQS);
5630 27 Sep 19 nicklas 665       json.put("error", error);
5630 27 Sep 19 nicklas 666       json.put("warning", warning);
5630 27 Sep 19 nicklas 667       return json;
5630 27 Sep 19 nicklas 668     }
5630 27 Sep 19 nicklas 669   }
5630 27 Sep 19 nicklas 670   
5630 27 Sep 19 nicklas 671 }