// $HeadURL$
// $Id$
//
// Copyright © 2006, 2010, 2011, 2012 by the President and Fellows of Harvard College.
//
// Screensaver is an open-source project developed by the ICCB-L and NSRB labs
// at Harvard Medical School. This software is distributed under the terms of
// the GNU General Public License.
package edu.harvard.med.screensaver.ui.libraries;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import com.google.common.collect.Lists;
import org.apache.log4j.Logger;
import org.springframework.transaction.annotation.Transactional;
import edu.harvard.med.screensaver.db.Criterion;
import edu.harvard.med.screensaver.db.DAOTransaction;
import edu.harvard.med.screensaver.db.EntityInflator;
import edu.harvard.med.screensaver.db.GenericEntityDAO;
import edu.harvard.med.screensaver.db.datafetcher.AggregateDataFetcher;
import edu.harvard.med.screensaver.db.datafetcher.DataFetcherUtil;
import edu.harvard.med.screensaver.db.datafetcher.EntityDataFetcher;
import edu.harvard.med.screensaver.db.hqlbuilder.HqlBuilder;
import edu.harvard.med.screensaver.model.Volume;
import edu.harvard.med.screensaver.model.cherrypicks.CherryPickRequest;
import edu.harvard.med.screensaver.model.cherrypicks.LabCherryPick;
import edu.harvard.med.screensaver.model.libraries.Copy;
import edu.harvard.med.screensaver.model.libraries.Library;
import edu.harvard.med.screensaver.model.libraries.Plate;
import edu.harvard.med.screensaver.model.libraries.PlateStatus;
import edu.harvard.med.screensaver.model.libraries.Well;
import edu.harvard.med.screensaver.model.libraries.WellCopy;
import edu.harvard.med.screensaver.model.libraries.WellKey;
import edu.harvard.med.screensaver.model.libraries.WellVolume;
import edu.harvard.med.screensaver.model.libraries.WellVolumeAdjustment;
import edu.harvard.med.screensaver.model.meta.PropertyPath;
import edu.harvard.med.screensaver.model.users.AdministratorUser;
import edu.harvard.med.screensaver.model.users.ScreensaverUserRole;
import edu.harvard.med.screensaver.service.libraries.WellCopyVolumeAdjuster;
import edu.harvard.med.screensaver.ui.arch.datatable.column.BooleanColumn;
import edu.harvard.med.screensaver.ui.arch.datatable.column.IntegerColumn;
import edu.harvard.med.screensaver.ui.arch.datatable.column.TableColumn;
import edu.harvard.med.screensaver.ui.arch.datatable.column.TextColumn;
import edu.harvard.med.screensaver.ui.arch.datatable.column.VolumeColumn;
import edu.harvard.med.screensaver.ui.arch.datatable.model.InMemoryDataModel;
import edu.harvard.med.screensaver.ui.arch.searchresults.EntityBasedEntitySearchResults;
public class WellCopyVolumeSearchResults extends EntityBasedEntitySearchResults<WellCopy,String>
{
private static Logger log = Logger.getLogger(WellCopyVolumeSearchResults.class);
private GenericEntityDAO _dao;
private LibraryViewer _libraryViewer;
private WellViewer _wellViewer;
private WellVolumeSearchResults _wellVolumeSearchResults;
private WellCopyVolumeAdjuster _wellCopyVolumeAdjuster;
private Map<WellCopy,Volume> _newRemainingVolumes = new HashMap<WellCopy,Volume>();
private String _wellVolumeAdjustmentActivityComments;
private TableColumn<WellCopy,?> _newRemainingVolumeColumn;
private TableColumn<WellCopy,?> _withdrawalsAdjustmentsColumn;
/**
* @motivation for CGLIB2
*/
protected WellCopyVolumeSearchResults()
{
}
public WellCopyVolumeSearchResults(GenericEntityDAO dao,
LibraryViewer libraryViewer,
WellViewer wellViewer,
WellVolumeSearchResults wellVolumeSearchResults,
WellCopyVolumeAdjuster wellCopyVolumeAdjuster)
{
_dao = dao;
_wellVolumeSearchResults = wellVolumeSearchResults;
_wellCopyVolumeAdjuster = wellCopyVolumeAdjuster;
_libraryViewer = libraryViewer;
_wellViewer = wellViewer;
setEditingRole(ScreensaverUserRole.LIBRARIES_ADMIN);
}
public void searchWells(final Set<WellKey> wellKeys)
{
setTitle("Well Copy Volumes Search Result");
_wellVolumeSearchResults.setTitle("Well Volumes Search Result");
EntityDataFetcher<WellVolumeAdjustment,Integer> wvaFetcher =
new EntityDataFetcher<WellVolumeAdjustment,Integer>(WellVolumeAdjustment.class, _dao) {
@Override
public void addDomainRestrictions(HqlBuilder hql)
{
Set<String> wellKeyStrings = new HashSet<String>();
for (WellKey wellKey : wellKeys) {
wellKeyStrings.add(wellKey.toString());
}
hql.whereIn(getRootAlias(), "well.id", wellKeyStrings);
}
};
addRelationshipsToFetch(wvaFetcher);
EntityDataFetcher<WellCopy,String> wellCopyDataFetcher =
new AggregateDataFetcher<WellCopy,String,WellVolumeAdjustment,Integer>(WellCopy.class, _dao, wvaFetcher) {
@Override
protected SortedSet<WellCopy> aggregateData(List<WellVolumeAdjustment> nonAggregatedData)
{
SortedSet<WellCopy> result = new TreeSet<WellCopy>();
for (WellKey wellKey : wellKeys) {
Well well = _dao.findEntityById(Well.class, wellKey.toString(), true, Well.library.to(Library.copies).to(Copy.plates));
makeWellCopyVolumes(well, result);
}
return aggregateWellVolumeAdjustments(result, nonAggregatedData);
}
};
doInitialize(wellCopyDataFetcher);
}
public void searchWellsForLibrary(final Library library)
{
setTitle("Well Copy Volumes for library " + library.getLibraryName());
EntityDataFetcher<WellVolumeAdjustment,Integer> wvaFetcher =
new EntityDataFetcher<WellVolumeAdjustment,Integer>(WellVolumeAdjustment.class, _dao) {
@Override
public void addDomainRestrictions(HqlBuilder hql)
{
DataFetcherUtil.addDomainRestrictions(hql, WellVolumeAdjustment.well.to(Well.library), library, getRootAlias());
}
};
addRelationshipsToFetch(wvaFetcher);
EntityDataFetcher<WellCopy,String> wellCopyDataFetcher =
new AggregateDataFetcher<WellCopy,String,WellVolumeAdjustment,Integer>(WellCopy.class, _dao, wvaFetcher) {
@Override
@Transactional
protected SortedSet<WellCopy> aggregateData(List<WellVolumeAdjustment> nonAggregatedData)
{
// reload library and eager fetch some relationships, allowing makeWellCopyVolumes() to work
final Library[] library2 = new Library[1];
_dao.doInTransaction(new DAOTransaction() {
public void runTransaction() {
library2[0] = _dao.reloadEntity(library, true, Library.wells);
_dao.needReadOnly(library2[0], Library.copies.to(Copy.plates));
}
});
return aggregateWellVolumeAdjustments(makeWellCopyVolumes(library2[0], new TreeSet<WellCopy>()),
nonAggregatedData);
}
};
doInitialize(wellCopyDataFetcher);
}
private void doInitialize(EntityDataFetcher<WellCopy,String> wellCopyDataFetcher)
{
initialize(new InMemoryDataModel<WellCopy>(wellCopyDataFetcher));
EntityDataFetcher<WellVolume,String> wellVolumeDataFetcher =
new AggregateDataFetcher<WellVolume,String,WellCopy,String>(WellVolume.class, _dao, wellCopyDataFetcher) {
@Override
protected SortedSet<WellVolume> aggregateData(List<WellCopy> nonAggregatedData)
{
return aggregateWellCopies(nonAggregatedData);
}
};
_wellVolumeSearchResults.initialize(new InMemoryDataModel<WellVolume>(wellVolumeDataFetcher));
}
public void searchWellsForCherryPickRequest(CherryPickRequest cherryPickRequest,
boolean forUnfulfilledOnly)
{
// note: it would be nicer to select the WVAs by their parent CPR, but we
// can't do this since Well.cherryPickRequests relationship does not exist in
// our model; instead we just find all the wells for CPR and delegate to the
// searchWells() method
Set<WellKey> wellKeys = new HashSet<WellKey>();
EntityInflator<CherryPickRequest> inflator =
new EntityInflator<CherryPickRequest>(_dao, cherryPickRequest, true).need(CherryPickRequest.labCherryPicks.to(LabCherryPick.sourceWell));
if (forUnfulfilledOnly) {
inflator.need(CherryPickRequest.labCherryPicks.to(LabCherryPick.wellVolumeAdjustments));
}
cherryPickRequest = inflator.inflate();
for (LabCherryPick labCherryPick : cherryPickRequest.getLabCherryPicks()) {
if (!forUnfulfilledOnly || labCherryPick.isUnfulfilled()) {
wellKeys.add(labCherryPick.getSourceWell().getWellKey());
}
}
searchWells(wellKeys);
setTitle("Well Copy Volumes for cherry pick request " + cherryPickRequest.getCherryPickRequestNumber() + " lab cherry picks");
_wellVolumeSearchResults.setTitle("Well Volumes for cherry pick request " + cherryPickRequest.getCherryPickRequestNumber() +
" lab cherry picks");
}
@Override
protected List<? extends TableColumn<WellCopy,?>> buildColumns()
{
ArrayList<TableColumn<WellCopy,?>> columns = new ArrayList<TableColumn<WellCopy,?>>();
columns.add(new TextColumn<WellCopy>(
"Library", "The library containing the well", TableColumn.UNGROUPED) {
@Override
public String getCellValue(WellCopy wellCopy) { return wellCopy.getWell().getLibrary().getLibraryName(); }
@Override
public boolean isCommandLink() { return true; }
@Override
public Object cellAction(WellCopy wellCopy) { return _libraryViewer.viewEntity(wellCopy.getWell().getLibrary()); }
});
columns.add(new IntegerColumn<WellCopy>(
"Plate", "The number of the plate the well is located on", TableColumn.UNGROUPED) {
@Override
public Integer getCellValue(WellCopy wellCopy) { return wellCopy.getWell().getPlateNumber(); }
});
columns.add(new TextColumn<WellCopy>(
"Well", "The plate coordinates of the well", TableColumn.UNGROUPED) {
@Override
public String getCellValue(WellCopy wellCopy) { return wellCopy.getWell().getWellName(); }
@Override
public boolean isCommandLink() { return true; }
@Override
public Object cellAction(WellCopy wellCopy) { return _wellViewer.viewEntity(wellCopy.getWell()); }
});
columns.add(new TextColumn<WellCopy>(
"Copy", "The name of the library plate copy", TableColumn.UNGROUPED) {
@Override
public String getCellValue(WellCopy wellCopy) { return wellCopy.getCopy().getName(); }
// TODO
// @Override
// public boolean isCommandLink() { return true; }
// @Override
// public Object cellAction(WellCopyVolume wellCopy) { return _libraryViewer.viewLibraryCopyVolumes(wellVolume.getWell(), WellCopyVolumeSearchResults.this); }
});
TableColumn<WellCopy,Boolean> col =
new BooleanColumn<WellCopy>("Is Retired", "Has this copy been retired?", TableColumn.UNGROUPED)
{
@Override
public Boolean getCellValue(WellCopy wc)
{
Plate plate = wc.getCopy().getPlates().get(wc.getWell().getPlateNumber());
return plate != null && plate.getStatus() == PlateStatus.RETIRED;
}
};
col.clearCriteria();
col.addCriterion(new Criterion<Boolean>(Criterion.Operator.EQUAL, Boolean.FALSE));
columns.add(col);
columns.add(new VolumeColumn<WellCopy>(
"Initial Volume", "The initial volume of this well copy", TableColumn.UNGROUPED) {
@Override
public Volume getCellValue(WellCopy wellCopy) { return wellCopy.getInitialVolume(); }
});
columns.add(new VolumeColumn<WellCopy>(
"Consumed Volume", "The volume already used from this well copy", TableColumn.UNGROUPED) {
@Override
public Volume getCellValue(WellCopy wellCopy) { return wellCopy.getConsumedVolume(); }
});
columns.add(new VolumeColumn<WellCopy>(
"Remaining Volume", "The remaining volume of this well copy", TableColumn.UNGROUPED) {
@Override
public Volume getCellValue(WellCopy wellCopy) { return wellCopy.getRemainingVolume(); }
});
_withdrawalsAdjustmentsColumn = new IntegerColumn<WellCopy>(
"Withdrawals/Adjustments", "The number of withdrawals and administrative adjustments made from this well copy", TableColumn.UNGROUPED) {
@Override
public Integer getCellValue(WellCopy wellCopy) { return wellCopy.getWellVolumeAdjustments().size(); }
@Override
public boolean isCommandLink() { return getRowData().getWellVolumeAdjustments().size() > 0; }
@Override
public Object cellAction(WellCopy entity)
{
return null;
//return showRowDetail();
}
};
columns.add(_withdrawalsAdjustmentsColumn);
_newRemainingVolumeColumn = new VolumeColumn<WellCopy>(
"New Remaining Volume", "Enter new remaining volume", TableColumn.UNGROUPED) {
@Override
public Volume getCellValue(WellCopy wellCopy) { return _newRemainingVolumes.get(wellCopy); }
@Override
public void setCellValue(WellCopy wellCopy, Volume volume)
{
if (volume != null) {
_newRemainingVolumes.put(wellCopy, volume);
}
else {
_newRemainingVolumes.remove(wellCopy);
}
}
@Override
public boolean isEditable() { return true; }
};
columns.add(_newRemainingVolumeColumn);
_newRemainingVolumeColumn.setVisible(false);
// TableColumnManager<WellCopy> columnManager = getColumnManager();
// columnManager.addCompoundSortColumns(columnManager.getColumn("Library"), columnManager.getColumn("Plate"), columnManager.getColumn("Well"), columnManager.getColumn("Copy"));
// columnManager.addCompoundSortColumns(columnManager.getColumn("Plate"), columnManager.getColumn("Well"), columnManager.getColumn("Copy"));
// columnManager.addCompoundSortColumns(columnManager.getColumn("Well"), columnManager.getColumn("Plate"), columnManager.getColumn("Copy"));
// columnManager.addCompoundSortColumns(columnManager.getColumn("Copy"), columnManager.getColumn("Plate"), columnManager.getColumn("Well"), columnManager.getColumn("Copy"));
// columnManager.addCompoundSortColumns(columnManager.getColumn("Initial Volume"), columnManager.getColumn("Plate"), columnManager.getColumn("Well"), columnManager.getColumn("Copy"));
// columnManager.addCompoundSortColumns(columnManager.getColumn("Consumed Volume"), columnManager.getColumn("Plate"), columnManager.getColumn("Well"), columnManager.getColumn("Copy"));
// columnManager.addCompoundSortColumns(columnManager.getColumn("Remaining Volume"), columnManager.getColumn("Plate"), columnManager.getColumn("Well"), columnManager.getColumn("Copy"));
// return _columns;
return columns;
}
public String getWellVolumeAdjustmentActivityComments()
{
return _wellVolumeAdjustmentActivityComments;
}
public void setWellVolumeAdjustmentActivityComments(String wellVolumeAdjustmentActivityComments)
{
_wellVolumeAdjustmentActivityComments = wellVolumeAdjustmentActivityComments;
}
//@Override
//protected void makeRowDetail(WellCopy wcv)
//{
// List<WellVolumeAdjustment> wvas = new ArrayList<WellVolumeAdjustment>(wcv.getWellVolumeAdjustments().size());
// for (WellVolumeAdjustment wva : wcv.getWellVolumeAdjustments()) {
// WellVolumeAdjustment wva2 = _dao.reloadEntity(wva,
// true,
// "well",
// "copy",
// "labCherryPick.wellVolumeAdjustments",
// "labCherryPick.cherryPickRequest",
// "labCherryPick.assayPlate.cherryPickLiquidTransfer",
// "wellVolumeCorrectionActivity.performedBy");
// wvas.add(wva2);
// }
// getRowDetail().setContents(wvas);
//}
@Override
public void doEdit()
{
_newRemainingVolumes.clear();
_wellVolumeAdjustmentActivityComments = null;
}
@Override
public void doSave()
{
_wellCopyVolumeAdjuster.adjustWellCopyVolumes((AdministratorUser) getCurrentScreensaverUser().getScreensaverUser(),
_newRemainingVolumes,
getWellVolumeAdjustmentActivityComments());
if (_newRemainingVolumes.size() > 0) {
reload();
_wellVolumeSearchResults.reload();
// TODO: this showMessage() call prevents data table from being rendered with updated values!
//showMessage("libraries.updatedWellVolumes", new Integer(_newRemainingVolumes.size()));
}
else {
showMessage("libraries.updatedNoWellVolumes");
}
}
@Override
protected void setEditMode(boolean isEditMode)
{
super.setEditMode(isEditMode);
_withdrawalsAdjustmentsColumn.setVisible(!isEditMode);
_newRemainingVolumeColumn.setVisible(isEditMode);
// TODO: cancel the refetch() caused by changing column visibility
}
// private methods
private SortedSet<WellCopy> makeWellCopyVolumes(Library library,
SortedSet<WellCopy> wellCopyVolumes)
{
for (Well well : library.getWells()) {
makeWellCopyVolumes(well, wellCopyVolumes);
}
return wellCopyVolumes;
}
private SortedSet<WellCopy> makeWellCopyVolumes(Well well,
SortedSet<WellCopy> wellCopyVolumes)
{
for (Copy copy : well.getLibrary().getCopies()) {
wellCopyVolumes.add(new WellCopy(well, copy));
}
return wellCopyVolumes;
}
// private List<WellCopy> makeWellCopyVolumes(Copy copy, List<WellCopy> wellVolumes)
// {
// for (int plateNumber = copy.getLibrary().getStartPlate(); plateNumber <= copy.getLibrary().getEndPlate(); ++plateNumber) {
// makeWellCopyVolumes(copy, plateNumber, wellVolumes);
// }
// return wellVolumes;
// }
//
//
// private List<WellCopy> makeWellCopyVolumes(Copy copy, int plateNumber, List<WellCopy> wellVolumes)
// {
// for (int iRow = 0; iRow < Well.PLATE_ROWS; ++iRow) {
// for (int iCol = 0; iCol < Well.PLATE_COLUMNS; ++iCol) {
// wellVolumes.add(new WellCopy(findWell(new WellKey(plateNumber, iRow, iCol)), copy));
// }
// }
// return wellVolumes;
// }
/**
* Aggregates wellVolumeAdjustments into the provided wellCopyVolumes. The
* wellCopyVolumes parameter is necessary in order to report the full set well
* copies including those that have zero well volume adjustments.
*/
private SortedSet<WellCopy> aggregateWellVolumeAdjustments(SortedSet<WellCopy> wellCopyVolumes,
List<WellVolumeAdjustment> wellVolumeAdjustments)
{
Collections.sort(wellVolumeAdjustments,
new Comparator<WellVolumeAdjustment>() {
public int compare(WellVolumeAdjustment wva1, WellVolumeAdjustment wva2)
{
int result = wva1.getWell().compareTo(wva2.getWell());
if (result == 0) {
result = wva1.getCopy().getName().compareTo(wva2.getCopy().getName());
}
return result;
}
});
Iterator<WellCopy> wcvIter = wellCopyVolumes.iterator();
Iterator<WellVolumeAdjustment> wvaIter = wellVolumeAdjustments.iterator();
if (wcvIter.hasNext()) {
WellCopy wellCopyVolume = wcvIter.next();
while (wvaIter.hasNext()) {
WellVolumeAdjustment wellVolumeAdjustment = wvaIter.next();
while (!wellCopyVolume.getWell().equals(wellVolumeAdjustment.getWell()) ||
!wellCopyVolume.getCopy().equals(wellVolumeAdjustment.getCopy())) {
if (!wcvIter.hasNext()) {
throw new IllegalArgumentException("wellVolumeAdjustments exist for wells that were not in wellCopyVolumes: " +
wellVolumeAdjustment.getWell() + ":" + wellVolumeAdjustment.getCopy().getName());
}
wellCopyVolume = wcvIter.next();
}
wellCopyVolume.addWellVolumeAdjustment(wellVolumeAdjustment);
}
}
return wellCopyVolumes;
}
private SortedSet<WellVolume> aggregateWellCopies(List<WellCopy> nonAggregatedData)
{
TreeSet<WellVolume> result = new TreeSet<WellVolume>();
Collections.sort(nonAggregatedData);
HashSet<WellCopy> wellCopiesGroup = new HashSet<WellCopy>();
Well groupByWell = null;
for (WellCopy wellCopy : nonAggregatedData) {
if (! wellCopy.getWell().equals(groupByWell)) {
if (groupByWell != null) {
result.add(new WellVolume(groupByWell, wellCopiesGroup));
}
wellCopiesGroup.clear();
groupByWell = wellCopy.getWell();
}
wellCopiesGroup.add(wellCopy);
}
// add the last group
if (groupByWell != null) {
result.add(new WellVolume(groupByWell, wellCopiesGroup));
}
return result;
}
private void addRelationshipsToFetch(EntityDataFetcher<WellVolumeAdjustment,Integer> wvaFetcher)
{
List<PropertyPath<WellVolumeAdjustment>> relationships = Lists.newArrayList();
relationships.add(WellVolumeAdjustment.well.to(Well.library).toFullEntity());
relationships.add(WellVolumeAdjustment.copy.to(Copy.plates).toFullEntity());
wvaFetcher.setPropertiesToFetch(relationships);
}
@Override
public void searchAll()
{
// TODO Auto-generated method stub
}
}