/*************************************************************************
* *
* This file is part of the 20n/act project. *
* 20n/act enables DNA prediction for synthetic biology/bioengineering. *
* Copyright (C) 2017 20n Labs, Inc. *
* *
* Please direct all queries to act@20n.com. *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*************************************************************************/
package com.act.lcms.db.model;
import com.act.lcms.MS1;
import com.act.lcms.XZ;
import com.act.lcms.db.io.DB;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang3.StringUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class MS1ScanForWellAndMassCharge extends BaseDBModel<MS1ScanForWellAndMassCharge> implements Serializable {
private static final long serialVersionUID = 8606939292070032578L;
public static final String TABLE_NAME = "ms1_for_well_and_mass_charge";
protected static final MS1ScanForWellAndMassCharge INSTANCE = new MS1ScanForWellAndMassCharge();
public static MS1ScanForWellAndMassCharge getInstance() {
return INSTANCE;
}
private enum DB_FIELD implements DBFieldEnumeration {
ID(1, -1, "id"),
PLATE_ID(2, 1, "plate_id"),
PLATE_ROW(3, 2, "plate_row"),
PLATE_COLUMN(4, 3, "plate_column"),
USE_SNR(5, 4, "use_snr"),
SCAN_FILE(6, 5, "scan_file"),
CHEMICAL_NAME(7, 6, "chemical_name"),
METLIN_IONS(8, 7, "metlin_ions"),
IONS_TO_SPECTRA(9, 8, "ions_to_spectra"),
IONS_TO_INTEGRAL(10, 9, "ions_to_integral"),
IONS_TO_MAX(11, 10, "ions_to_max"),
IONS_TO_LOG_SNR(12, 11, "ions_to_log_snr"),
IONS_TO_AVG_SIGNAL(13, 12, "ions_to_avg_signal"),
IONS_TO_AVG_AMBIENT(14, 13, "ions_to_avg_ambient"),
INDIVIDUAL_MAX_INTENSITIES(15, 14, "individual_max_intensities"),
MAX_Y_AXIS(16, 15, "max_y_axis");
private final int offset;
private final int insertUpdateOffset;
private final String fieldName;
DB_FIELD(int offset, int insertUpdateOffset, String fieldName) {
this.offset = offset;
this.insertUpdateOffset = insertUpdateOffset;
this.fieldName = fieldName;
}
@Override
public int getOffset() {
return offset;
}
@Override
public int getInsertUpdateOffset() {
return insertUpdateOffset;
}
@Override
public String getFieldName() {
return fieldName;
}
@Override
public String toString() {
return this.fieldName;
}
public static String[] names() {
DB_FIELD[] values = DB_FIELD.values();
String[] names = new String[values.length];
for (int i = 0; i < values.length; i++) {
names[i] = values[i].getFieldName();
}
return names;
}
}
protected static final List<String> ALL_FIELDS = Collections.unmodifiableList(Arrays.asList(DB_FIELD.names()));
// id is auto-generated on insertion.
protected static final List<String> INSERT_UPDATE_FIELDS =
Collections.unmodifiableList(ALL_FIELDS.subList(1, ALL_FIELDS.size()));
@Override
public String getTableName() {
return TABLE_NAME;
}
@Override
public List<String> getAllFields() {
return ALL_FIELDS;
}
@Override
public List<String> getInsertUpdateFields() {
return INSERT_UPDATE_FIELDS;
}
protected static final String GET_BY_ID_QUERY = MS1ScanForWellAndMassCharge.getInstance().makeGetByIDQuery();
@Override
protected String getGetByIDQuery() {
return GET_BY_ID_QUERY;
}
protected static final String INSERT_QUERY = MS1ScanForWellAndMassCharge.getInstance().makeInsertQuery();
@Override
public String getInsertQuery() {
return INSERT_QUERY;
}
protected static final String UPDATE_QUERY = MS1ScanForWellAndMassCharge.getInstance().makeUpdateQuery();
@Override
public String getUpdateQuery() {
return UPDATE_QUERY;
}
@Override
protected List<MS1ScanForWellAndMassCharge> fromResultSet(ResultSet resultSet)
throws SQLException, IOException, ClassNotFoundException {
List<MS1ScanForWellAndMassCharge> results = new ArrayList<>();
while (resultSet.next()) {
Integer id = resultSet.getInt(DB_FIELD.ID.getOffset());
Integer plateId = resultSet.getInt(DB_FIELD.PLATE_ID.getOffset());
Integer plateRow = resultSet.getInt(DB_FIELD.PLATE_ROW.getOffset());
Integer plateColumn = resultSet.getInt(DB_FIELD.PLATE_COLUMN.getOffset());
Double maxYAxis = resultSet.getDouble(DB_FIELD.MAX_Y_AXIS.getOffset());
Boolean useSNR = resultSet.getBoolean(DB_FIELD.USE_SNR.getOffset());
String lcmsScanFilePath = resultSet.getString(DB_FIELD.SCAN_FILE.getOffset());
String chemicalName = resultSet.getString(DB_FIELD.CHEMICAL_NAME.getOffset());
List<String> metlinIons = MS1ScanForWellAndMassCharge.deserializeMetlinIons(
resultSet.getString(DB_FIELD.METLIN_IONS.getOffset()));
Map<String, List<XZ>> ionsToSpectra = MS1ScanForWellAndMassCharge.deserialize(
resultSet.getBytes(DB_FIELD.IONS_TO_SPECTRA.getOffset()));
Map<String, Double> ionsToIntegral = MS1ScanForWellAndMassCharge.deserialize(
resultSet.getBytes(DB_FIELD.IONS_TO_INTEGRAL.getOffset()));
Map<String, Double> ionsToMax = MS1ScanForWellAndMassCharge.deserialize(
resultSet.getBytes(DB_FIELD.IONS_TO_MAX.getOffset()));
Map<String, Double> ionsToLogSNR = MS1ScanForWellAndMassCharge.deserialize(
resultSet.getBytes(DB_FIELD.IONS_TO_LOG_SNR.getOffset()));
Map<String, Double> ionsToAvgSignal = MS1ScanForWellAndMassCharge.deserialize(
resultSet.getBytes(DB_FIELD.IONS_TO_AVG_SIGNAL.getOffset()));
Map<String, Double> ionsToAvgAmbient = MS1ScanForWellAndMassCharge.deserialize(
resultSet.getBytes(DB_FIELD.IONS_TO_AVG_AMBIENT.getOffset()));
Map<String, Double> individualMaxIntensities = MS1ScanForWellAndMassCharge.deserialize(
resultSet.getBytes(DB_FIELD.INDIVIDUAL_MAX_INTENSITIES.getOffset()));
results.add(new MS1ScanForWellAndMassCharge(id, plateId, plateColumn, plateRow, useSNR, lcmsScanFilePath, chemicalName,
metlinIons, ionsToSpectra, ionsToIntegral, ionsToMax, ionsToLogSNR, ionsToAvgSignal, ionsToAvgAmbient,
individualMaxIntensities, maxYAxis));
}
return results;
}
protected void bindInsertOrUpdateParameters(
PreparedStatement stmt, Integer plateId, Integer plateRow, Integer plateColumn,
Boolean useSNR, String lcmsScanFileDir, String chemicalName, List<String> metlinIons, Map<String,List<XZ>> ionsToSpectra,
Map<String, Double> ionsToIntegral, Map<String, Double> ionsToMax, Map<String, Double> ionsToLogSNR,
Map<String, Double> ionsToAvgSignal, Map<String, Double> ionsToAvgAmbient,
Map<String, Double> individualMaxIntensities, Double maxYAxis) throws SQLException, IOException {
stmt.setInt(DB_FIELD.PLATE_ID.getInsertUpdateOffset(), plateId);
stmt.setInt(DB_FIELD.PLATE_ROW.getInsertUpdateOffset(), plateRow);
stmt.setInt(DB_FIELD.PLATE_COLUMN.getInsertUpdateOffset(), plateColumn);
stmt.setBoolean(DB_FIELD.USE_SNR.getInsertUpdateOffset(), useSNR);
stmt.setString(DB_FIELD.SCAN_FILE.getInsertUpdateOffset(), lcmsScanFileDir);
stmt.setString(DB_FIELD.CHEMICAL_NAME.getInsertUpdateOffset(), chemicalName);
stmt.setString(DB_FIELD.METLIN_IONS.getInsertUpdateOffset(), OBJECT_MAPPER.writeValueAsString(metlinIons));
stmt.setBytes(DB_FIELD.IONS_TO_SPECTRA.getInsertUpdateOffset(), serialize(ionsToSpectra));
stmt.setBytes(DB_FIELD.IONS_TO_INTEGRAL.getInsertUpdateOffset(), serialize(ionsToIntegral));
stmt.setBytes(DB_FIELD.IONS_TO_LOG_SNR.getInsertUpdateOffset(), serialize(ionsToLogSNR));
stmt.setBytes(DB_FIELD.IONS_TO_AVG_AMBIENT.getInsertUpdateOffset(), serialize(ionsToAvgAmbient));
stmt.setBytes(DB_FIELD.IONS_TO_AVG_SIGNAL.getInsertUpdateOffset(), serialize(ionsToAvgSignal));
stmt.setBytes(DB_FIELD.INDIVIDUAL_MAX_INTENSITIES.getInsertUpdateOffset(), serialize(individualMaxIntensities));
stmt.setBytes(DB_FIELD.IONS_TO_MAX.getInsertUpdateOffset(), serialize(ionsToMax));
stmt.setDouble(DB_FIELD.MAX_Y_AXIS.getInsertUpdateOffset(), maxYAxis);
}
@Override
protected void bindInsertOrUpdateParameters(PreparedStatement stmt, MS1ScanForWellAndMassCharge ms1Result)
throws SQLException, IOException {
bindInsertOrUpdateParameters(
stmt, ms1Result.getPlateId(), ms1Result.getPlateRow(), ms1Result.getPlateColumn(), ms1Result.getUseSNR(),
ms1Result.getScanFilePath(), ms1Result.getChemicalName(), ms1Result.getMetlinIons(), ms1Result.getIonsToSpectra(),
ms1Result.getIonsToIntegral(), ms1Result.getIonsToMax(), ms1Result.getIonsToLogSNR(), ms1Result.getIonsToAvgSignal(),
ms1Result.getIonsToAvgAmbient(), ms1Result.getIndividualMaxIntensities(), ms1Result.getMaxYAxis());
}
private Integer id;
private Integer plateId;
private Integer plateRow;
private Integer plateColumn;
private Boolean useSNR;
private String lcmsScanFileDir;
private String chemicalName;
private List<String> metlinIons = new ArrayList<>();
private Map<String, List<XZ>> ionsToSpectra = new HashMap<>();
private Map<String, Double> ionsToIntegral = new HashMap<>();
private Map<String, Double> ionsToMax = new HashMap<>();
private Map<String, Double> ionsToLogSNR = new HashMap<>();
private Map<String, Double> ionsToAvgSignal = new HashMap<>();
private Map<String, Double> ionsToAvgAmbient = new HashMap<>();
private Map<String, Double> individualMaxIntensities = new HashMap<>();
private Double maxYAxis = 0.0d; // default to 0
public MS1ScanForWellAndMassCharge() {}
public MS1ScanForWellAndMassCharge(Integer id, Integer plateId, Integer plateColumn, Integer plateRow,
Boolean useSNR, String lcmsScanFileDir, String chemicalName,
List<String> metlinIons, Map<String, List<XZ>> ionsToSpectra,
Map<String, Double> ionsToIntegral, Map<String, Double> ionsToMax,
Map<String, Double> ionsToAvgSignal, Map<String, Double> ionsToAvgAmbient,
Map<String, Double> ionsToLogSNR, Map<String, Double> individualMaxIntensities,
Double maxYAxis) {
this.id = id;
this.useSNR = useSNR;
this.lcmsScanFileDir = lcmsScanFileDir;
this.chemicalName = chemicalName;
this.ionsToSpectra = ionsToSpectra;
this.ionsToAvgAmbient = ionsToAvgAmbient;
this.ionsToAvgSignal = ionsToAvgSignal;
this.ionsToIntegral = ionsToIntegral;
this.individualMaxIntensities = individualMaxIntensities;
this.maxYAxis = maxYAxis;
this.ionsToMax = ionsToMax;
this.plateRow = plateColumn;
this.plateId = plateId;
this.plateRow = plateRow;
this.ionsToLogSNR = ionsToLogSNR;
this.metlinIons = metlinIons;
}
/**
* Serialize an object to an array of Serialized, gzip'd bytes.
*
* Note that this returns a byte stream (a) to be symmetrical with deserialize, and (b) because we anticipate
* manifesting the entire byte array at some point so there's no advantage to streaming the results. If that changes
* and performance suffers from allocating the entire byte array, we can use byte streams instead (and we'll probably
* have bigger performance problems to deal with anyway).
*
* @param object The object to serialize
* @param <T> The type of the object (unbound to allow serialization of Maps, which sadly don't explicitly implement
* Serializable).
* @return A byte array representing a compressed object stream for the specified object.
* @throws IOException
*/
private static <T> byte[] serialize(T object) throws IOException {
ByteArrayOutputStream postGzipOutputStream = new ByteArrayOutputStream();
try (ObjectOutputStream out = new ObjectOutputStream(new GZIPOutputStream(postGzipOutputStream))) {
out.writeObject(object);
}
return postGzipOutputStream.toByteArray();
}
private static <T> T deserialize(byte[] object) throws IOException, ClassNotFoundException {
T map = null;
try (ObjectInputStream ois = new ObjectInputStream(new GZIPInputStream(new ByteArrayInputStream(object)))) {
// TODO: consider checking this cast? Though we'd just throw an exception anyway, so...
map = (T) ois.readObject();
}
return map;
}
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final TypeReference<List<String>> TYPE_REFERENCE_FOR_METLIN_IONS = new TypeReference<List<String>>() {};
private static List<String> deserializeMetlinIons(String serializedMetlinIons) throws IOException {
return OBJECT_MAPPER.readValue(serializedMetlinIons, TYPE_REFERENCE_FOR_METLIN_IONS);
}
public MS1ScanForWellAndMassCharge insert(
DB db, MS1ScanForWellAndMassCharge ms1Result)
throws SQLException, IOException {
Connection conn = db.getConn();
try (PreparedStatement stmt = conn.prepareStatement(MS1ScanForWellAndMassCharge.getInstance().getInsertQuery(),
Statement.RETURN_GENERATED_KEYS)) {
bindInsertOrUpdateParameters(stmt, ms1Result.getPlateId(), ms1Result.getPlateRow(),
ms1Result.getPlateColumn(), ms1Result.getUseSNR(), ms1Result.getScanFilePath(), ms1Result.getChemicalName(),
ms1Result.getMetlinIons(), ms1Result.getIonsToSpectra(), ms1Result.getIonsToIntegral(),
ms1Result.getIonsToMax(), ms1Result.getIonsToAvgSignal(), ms1Result.getIonsToAvgAmbient(),
ms1Result.getIonsToLogSNR(), ms1Result.getIndividualMaxIntensities(), ms1Result.getMaxYAxis());
stmt.executeUpdate();
try (ResultSet resultSet = stmt.getGeneratedKeys()) {
if (resultSet.next()) {
// Get auto-generated id.
int id = resultSet.getInt(1);
ms1Result.setId(id);
return ms1Result;
} else {
System.err.format("ERROR: could not retrieve autogenerated key for ms1 scan result\n");
return null;
}
}
}
}
// There are no update queries since this model is being used a compute chache of the results of the
// ms1 scan results, where the results are immutable given the same parameters.
// Extra access patterns.
public MS1ScanForWellAndMassCharge getByPlateIdPlateRowPlateColUseSnrScanFileChemical(
DB db, Plate plate, PlateWell well, Boolean useSnr, ScanFile scanFile, String chemicalName,
Map<String, Double> metlinIons, File lcmsFile) throws Exception {
// Pre-process the list of metlin ions
List<String> ions = new ArrayList<>();
ions.addAll(metlinIons.keySet());
Collections.sort(ions);
MS1ScanForWellAndMassCharge result =
this.getByPlateIdPlateRowPlateColUseSnrScanFileChemicalMetlinIonsFromDb(
db, plate, well, useSnr, scanFile.getFilename(), chemicalName, ions);
if (result == null) {
// couldn't find entry in the cache
MS1ScanForWellAndMassCharge construct = getMS1(lcmsFile.getAbsolutePath(), metlinIons);
construct.setPlateCoordinates(plate.getId(), well.getPlateRow(), well.getPlateColumn());
construct.setScanFilePath(scanFile.getFilename());
construct.setUseSnr(useSnr);
construct.setMetlinIons(ions);
construct.setChemicalName(chemicalName);
return insert(db, construct);
} else {
return result;
}
}
private static final String GET_BY_PLATE_ID_AND_PLATE_ROW_AND_PLATE_COL_AND_USE_SNR_AND_SCAN_FILE_PATH_AND_CHEMICAL_AND_METLIN_IONS =
StringUtils.join(new String[]{
"SELECT", StringUtils.join(MS1ScanForWellAndMassCharge.getInstance().getAllFields(), ','),
"from", MS1ScanForWellAndMassCharge.getInstance().getTableName(),
"where plate_id = ?",
" and plate_row = ?",
" and plate_column = ?",
" and use_snr = ?",
" and scan_file = ?",
" and chemical_name = ?",
" and metlin_ions = ?",
}, " ");
private MS1ScanForWellAndMassCharge getByPlateIdPlateRowPlateColUseSnrScanFileChemicalMetlinIonsFromDb(
DB db, Plate plate, PlateWell well, Boolean useSnr,
String scanFile, String chemicalName, List<String> metlinIons) throws Exception {
try (PreparedStatement stmt =
db.getConn().prepareStatement(
GET_BY_PLATE_ID_AND_PLATE_ROW_AND_PLATE_COL_AND_USE_SNR_AND_SCAN_FILE_PATH_AND_CHEMICAL_AND_METLIN_IONS)) {
stmt.setInt(1, plate.getId());
stmt.setInt(2, well.getPlateRow());
stmt.setInt(3, well.getPlateColumn());
stmt.setBoolean(4, useSnr);
stmt.setString(5, scanFile);
stmt.setString(6, chemicalName);
stmt.setString(7, OBJECT_MAPPER.writeValueAsString(metlinIons));
try (ResultSet resultSet = stmt.executeQuery()) {
MS1ScanForWellAndMassCharge result = expectOneResult(resultSet,
String.format("plate_id = %d, plate_row = %d, plate_column = %d, use_snr = %s, scan_file = %s",
plate.getId(), well.getPlateRow(), well.getPlateColumn(), useSnr, scanFile));
return result;
}
}
}
private MS1ScanForWellAndMassCharge getMS1(String ms1File, Map<String, Double> metlinIons)
throws Exception {
MS1 ms1 = new MS1();
return ms1.getMS1(metlinIons, ms1File);
}
public Integer getPlateId() { return plateId; }
public Integer getPlateRow() { return plateRow; }
public Integer getPlateColumn() { return plateColumn; }
public String getScanFilePath() { return lcmsScanFileDir; }
public Boolean getUseSNR() {
return useSNR;
}
public Double getMaxYAxis() {
return maxYAxis;
}
public Map<String, Double> getIndividualMaxYAxis() {
return individualMaxIntensities;
}
public Double getMaxIntensityForIon(String ion) {
return ionsToMax.get(ion);
}
public List<String> getMetlinIons() {
return metlinIons;
}
public Map<String, List<XZ>> getIonsToSpectra() {
return ionsToSpectra;
}
public Map<String, Double> getIonsToIntegral() {
return ionsToIntegral;
}
public Map<String, Double> getIonsToAvgSignal() {
return ionsToAvgSignal;
}
public Map<String, Double> getIonsToMax() {
return ionsToMax;
}
public Map<String, Double> getIonsToLogSNR() {
return ionsToLogSNR;
}
public Map<String, Double> getIonsToAvgAmbient() {
return ionsToAvgAmbient;
}
public Map<String, Double> getIndividualMaxIntensities() {
return individualMaxIntensities;
}
public void setMaxIntensityForIon(String ion, Double max) {
this.ionsToMax.put(ion, max);
}
public Double getLogSNRForIon(String ion) {
return ionsToLogSNR.get(ion);
}
public String getChemicalName() {
return chemicalName;
}
public void setLogSNRForIon(String ion, Double logsnr) {
this.ionsToLogSNR.put(ion, logsnr);
}
public void setMetlinIons(List<String> metlinIons) {
this.metlinIons = metlinIons;
}
public void setAvgIntensityForIon(String ion, Double avgSignal, Double avgAmbient) {
this.ionsToAvgSignal.put(ion, avgSignal);
this.ionsToAvgAmbient.put(ion, avgAmbient);
}
// This function is set to private since it is only needed by this class to set
// the plate coordinates.
private void setPlateCoordinates(Integer plateId, Integer plateRow, Integer plateColumn) {
this.plateId = plateId;
this.plateRow = plateRow;
this.plateColumn = plateColumn;
}
public void setUseSnr(Boolean useSNR) {
this.useSNR = useSNR;
}
public void setScanFilePath(String scanFilePath) {
this.lcmsScanFileDir = scanFilePath;
}
public Double getIntegralForIon(String ion) {
return ionsToIntegral.get(ion);
}
public void setIntegralForIon(String ion, Double area) {
this.ionsToIntegral.put(ion, area);
}
public void setMaxYAxis(Double maxYAxis) {
this.maxYAxis = maxYAxis;
}
public void setIndividualMaxIntensities(Map<String, Double> individualMaxIntensities) {
this.individualMaxIntensities = individualMaxIntensities;
}
public void setChemicalName(String name) {
this.chemicalName = name;
}
}