// $HeadURL$
// $Id$
//
// Copyright © 2010 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.List;
import java.util.Set;
import javax.faces.model.SelectItem;
import org.apache.log4j.Logger;
import org.joda.time.LocalDate;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import edu.harvard.med.screensaver.ScreensaverConstants;
import edu.harvard.med.screensaver.db.GenericEntityDAO;
import edu.harvard.med.screensaver.model.BusinessRuleViolationException;
import edu.harvard.med.screensaver.model.Volume;
import edu.harvard.med.screensaver.model.VolumeUnit;
import edu.harvard.med.screensaver.model.libraries.Plate;
import edu.harvard.med.screensaver.model.libraries.PlateLocation;
import edu.harvard.med.screensaver.model.libraries.PlateStatus;
import edu.harvard.med.screensaver.model.libraries.PlateType;
import edu.harvard.med.screensaver.model.users.AdministratorUser;
import edu.harvard.med.screensaver.model.users.ScreensaverUser;
import edu.harvard.med.screensaver.model.users.ScreensaverUserRole;
import edu.harvard.med.screensaver.service.libraries.PlateUpdater;
import edu.harvard.med.screensaver.ui.arch.util.JSFUtils;
import edu.harvard.med.screensaver.ui.arch.util.UISelectOneBean;
import edu.harvard.med.screensaver.ui.arch.view.AbstractBackingBean;
import edu.harvard.med.screensaver.ui.arch.view.aspects.UICommand;
import edu.harvard.med.screensaver.util.NullSafeUtils;
import edu.harvard.med.screensaver.util.StringUtils;
public class LibraryCopyPlateBatchEditor extends AbstractBackingBean
{
public class LocationPartUISelectOneBean extends UISelectOneBean<String>
{
private String _locationPartName;
public LocationPartUISelectOneBean(String locationPartName)
{
super(Sets.newTreeSet(getDao().<PlateLocation,String>findDistinctPropertyValues(PlateLocation.class, locationPartName)),
null,
true);
_locationPartName = locationPartName;
}
public void refresh()
{
setDomain(getDao().<PlateLocation,String>findDistinctPropertyValues(PlateLocation.class, _locationPartName),
getSelection());
}
@Override
protected String getEmptyLabel()
{
return NO_CHANGE;
}
}
private static final Logger log = Logger.getLogger(LibraryCopyPlateBatchEditor.class);
private static final String NO_CHANGE = "<no change>";
private static final VolumeUnit DEFAULT_PLATE_WELL_VOLUME_UNITS = VolumeUnit.MICROLITERS;
private PlateUpdater _plateUpdater;
private GenericEntityDAO _dao;
private UISelectOneBean<PlateType> _plateType;
private UISelectOneBean<PlateStatus> _plateStatus;
private ActivityDTO _statusChangeActivity;
private LocationPartUISelectOneBean _room;
private LocationPartUISelectOneBean _freezer;
private LocationPartUISelectOneBean _shelf;
private LocationPartUISelectOneBean _bin;
private String _newPlateLocationRoom;
private String _newPlateLocationFreezer;
private String _newPlateLocationShelf;
private String _newPlateLocationBin;
private ActivityDTO _locationChangeActivity;
private UISelectOneBean<VolumeUnit> _volumeType;
private String _volumeValue;
private String _comments;
/** @motivation for CGLIB2 */
protected LibraryCopyPlateBatchEditor()
{}
public LibraryCopyPlateBatchEditor(GenericEntityDAO dao,
PlateUpdater plateUpdater)
{
super();
_dao = dao;
_plateUpdater = plateUpdater;
getIsPanelCollapsedMap().put("batchEdit", true);
}
public void initialize()
{
_plateType = null;
_plateStatus = null;
_statusChangeActivity = new ActivityDTO();
_statusChangeActivity.setDateOfActivity(new LocalDate());
_statusChangeActivity.setPerformedBy(getScreensaverUser());
_volumeValue = null;
_volumeType = null;
_room = null;
_freezer = null;
_shelf = null;
_bin = null;
_locationChangeActivity = new ActivityDTO();
_locationChangeActivity.setDateOfActivity(new LocalDate());
_locationChangeActivity.setPerformedBy(getScreensaverUser());
_newPlateLocationRoom = null;
_newPlateLocationFreezer = null;
_newPlateLocationShelf = null;
_newPlateLocationBin = null;
_comments = null;
}
private boolean validate()
{
boolean valid = true;
try {
Volume.makeVolume(getVolumeValue(),
getVolumeType().getSelection());
}
catch (ArithmeticException e) {
showMessage("invalidUserInput", "Volume: number format error: allowed range (>= 1.0 nL, < 1.0L), in 1 nL increments", e.getLocalizedMessage());
valid = false;
}
catch (Exception e) {
showMessage("invalidUserInput", "Volume", e.getLocalizedMessage());
valid = false;
}
if (getPlateStatus().getSelection() != null) {
if (_statusChangeActivity.getPerformedBy() == null) {
showMessage("requiredValue", "plate status change performed by");
valid = false;
}
if (_statusChangeActivity.getDateOfActivity() == null) {
showMessage("requiredValue", "plate status change date");
valid = false;
}
}
if (getRoom().getSelection() != null || getNewPlateLocationRoom() != null ||
getFreezer().getSelection() != null || getNewPlateLocationFreezer() != null ||
getShelf().getSelection() != null || getNewPlateLocationShelf() != null ||
getBin().getSelection() != null || getNewPlateLocationBin() != null) {
if (_locationChangeActivity.getPerformedBy() == null) {
showMessage("requiredValue", "plate location change performed by");
valid = false;
}
if (_locationChangeActivity.getDateOfActivity() == null) {
showMessage("requiredValue", "plate location change date");
valid = false;
}
if (getPlateStatus().getSelection() != null && !!!getPlateStatus().getSelection().isInventoried()) {
showMessage("libraries.plateStatusProhibitsNewLocation", getPlateStatus().getSelection());
valid = false;
}
}
return valid;
}
@UICommand
@Transactional
public boolean updatePlates(Set<Plate> plates)
{
ScreensaverUser screensaverUser = getCurrentScreensaverUser().getScreensaverUser();
if (!(screensaverUser instanceof AdministratorUser) ||
!((AdministratorUser) screensaverUser).isUserInRole(ScreensaverUserRole.LIBRARY_COPIES_ADMIN)) {
throw new BusinessRuleViolationException("only library copies administrators can edit library copy plates");
}
if (!validate()) {
return false;
}
int modifiedCount = 0;
AdministratorUser adminUser =
getDao().reloadEntity((AdministratorUser) getScreensaverUser());
for (Plate plate : plates) {
boolean modified = false;
// ensures that all plateUpdate method calls are working with the same
// instance, which is necessary if a method needs to inspect that state of
// a plate, as changed by an earlier method call
plate = getDao().reloadEntity(plate);
if (!StringUtils.isEmpty(getVolumeValue())) {
Volume newVolume = new Volume(getVolumeValue(), getVolumeType().getSelection());
modified |= _plateUpdater.updateWellVolume(plate, newVolume, adminUser);
}
if (getPlateType().getSelection() != null) {
modified |= _plateUpdater.updatePlateType(plate, getPlateType().getSelection(), adminUser);
}
if (getPlateStatus().getSelection() != null) {
boolean statusUpdated = _plateUpdater.updatePlateStatus(plate,
getPlateStatus().getSelection(),
adminUser,
(AdministratorUser) _statusChangeActivity.getPerformedBy(),
_statusChangeActivity.getDateOfActivity());
if(statusUpdated) {
modified=true;
// recalculate the copy stats for the plate concentrations as copy
// stats only consider onsite plates
// TODO: create a transient flag or other check so that this is not
// repeated for every plate in the copy
_plateUpdater.updatePrimaryPlateConcentrations(getDao().reloadEntity(plate.getCopy()));
}
}
if (!StringUtils.isEmpty(_comments)) {
_plateUpdater.addComment(plate, adminUser, _comments);
modified = true;
}
PlateLocation newLocation = makeNewLocation(plate);
if (newLocation != null) {
modified |= _plateUpdater.updatePlateLocation(plate,
newLocation,
adminUser,
(AdministratorUser) _locationChangeActivity.getPerformedBy(),
_locationChangeActivity.getDateOfActivity());
}
if (modified) {
// note: if no other properties were modified, the facility ID will not be modified,
// even if it is not up-to-date; this is less confusing to the user, who would
// otherwise expect only properties with specified values to affect the modified count
_plateUpdater.updateFacilityId(plate, adminUser);
}
if (modified) {
++modifiedCount;
getDao().flush(); // HACK: due to commented-out flush() in PlateUpdate.findPlateLocation()
}
}
_plateUpdater.validateLocations();
showMessage("libraries.updatedPlates", modifiedCount, plates.size());
getRoom().refresh();
getFreezer().refresh();
getShelf().refresh();
getBin().refresh();
return true;
}
private PlateLocation makeNewLocation(Plate plate)
{
PlateLocation oldLocation = plate.getLocation();
String room = getNewPlateLocationRoom() != null ? getNewPlateLocationRoom()
: getRoom().getSelection() != null
? getRoom().getSelection() : null;
String freezer = getNewPlateLocationFreezer() != null ? getNewPlateLocationFreezer()
: getFreezer().getSelection() != null ? getFreezer().getSelection() : null;
String shelf = getNewPlateLocationShelf() != null ? getNewPlateLocationShelf()
: getShelf().getSelection() != null
? getShelf().getSelection() : null;
String bin = getNewPlateLocationBin() != null ? getNewPlateLocationBin()
: getBin().getSelection() != null
? getBin().getSelection() : null;
if (room == null && freezer == null && shelf == null && bin == null) {
return null;
}
if (oldLocation != null) {
if (room == null) {
room = oldLocation.getRoom();
}
if (freezer == null) {
freezer = oldLocation.getFreezer();
}
if (shelf == null) {
shelf = oldLocation.getShelf();
}
if (bin == null) {
bin = oldLocation.getBin();
}
if (NullSafeUtils.nullSafeEquals(room, oldLocation.getRoom()) &&
NullSafeUtils.nullSafeEquals(freezer, oldLocation.getFreezer()) &&
NullSafeUtils.nullSafeEquals(shelf, oldLocation.getShelf()) &&
NullSafeUtils.nullSafeEquals(bin, oldLocation.getBin())) {
// no change
return null;
}
}
else {
room = room == null ? PlateUpdater.NO_ROOM : room;
freezer = freezer == null ? PlateUpdater.NO_FREEZER : freezer;
shelf = shelf == null ? PlateUpdater.NO_SHELF : shelf;
bin = bin == null ? PlateUpdater.NO_BIN : bin;
}
PlateLocation newLocation = new PlateLocation(room, freezer, shelf, bin);
return newLocation;
}
private GenericEntityDAO getDao()
{
return _dao;
}
public String getVolumeValue()
{
return _volumeValue;
}
public void setVolumeValue(String value)
{
_volumeValue = value;
}
public UISelectOneBean<VolumeUnit> getVolumeType()
{
try {
if (_volumeType == null) {
Volume v = null;
VolumeUnit unit = (v == null ? DEFAULT_PLATE_WELL_VOLUME_UNITS : v.getUnits());
_volumeType = new UISelectOneBean<VolumeUnit>(VolumeUnit.DISPLAY_VALUES, unit)
{
@Override
protected String makeLabel(VolumeUnit t)
{
return t.getValue();
}
};
}
return _volumeType;
}
catch (Exception e) {
log.error("err: " + e);
return null;
}
}
public UISelectOneBean<PlateType> getPlateType()
{
if (_plateType == null) {
_plateType = new UISelectOneBean<PlateType>(Lists.newArrayList(PlateType.values()), null, true) {
@Override
protected String getEmptyLabel()
{
return NO_CHANGE;
}
};
}
return _plateType;
}
public UISelectOneBean<PlateStatus> getPlateStatus()
{
if (_plateStatus == null) {
_plateStatus = new UISelectOneBean<PlateStatus>(Lists.newArrayList(PlateStatus.values()), null, true) {
@Override
protected String getEmptyLabel()
{
return NO_CHANGE;
}
};
}
return _plateStatus;
}
public ActivityDTO getStatusChangeActivity()
{
return _statusChangeActivity;
}
public ActivityDTO getLocationChangeActivity()
{
return _locationChangeActivity;
}
public LocationPartUISelectOneBean getRoom()
{
if (_room == null) {
_room = new LocationPartUISelectOneBean("room");
}
return _room;
}
public LocationPartUISelectOneBean getFreezer()
{
if (_freezer == null) {
_freezer = new LocationPartUISelectOneBean("freezer");
}
return _freezer;
}
public LocationPartUISelectOneBean getShelf()
{
if (_shelf == null) {
_shelf = new LocationPartUISelectOneBean("shelf");
}
return _shelf;
}
public LocationPartUISelectOneBean getBin()
{
if (_bin == null) {
_bin = new LocationPartUISelectOneBean("bin");
}
return _bin;
}
public List<SelectItem> getAdministratorUserSelectItems()
{
return JSFUtils.createUISelectItemsWithEmptySelection(Sets.newTreeSet(getDao().findAllEntitiesOfType(AdministratorUser.class)),
ScreensaverConstants.REQUIRED_VOCAB_FIELD_PROMPT,
ScreensaverUser.ToFullNameLastFirstAndId);
}
public String getComments()
{
return _comments;
}
public void setComments(String comments)
{
_comments = comments;
}
public String getNewPlateLocationRoom()
{
return _newPlateLocationRoom;
}
public void setNewPlateLocationRoom(String newPlateLocationRoom)
{
_newPlateLocationRoom = newPlateLocationRoom;
}
public String getNewPlateLocationFreezer()
{
return _newPlateLocationFreezer;
}
public void setNewPlateLocationFreezer(String newPlateLocationFreezer)
{
_newPlateLocationFreezer = newPlateLocationFreezer;
}
public String getNewPlateLocationShelf()
{
return _newPlateLocationShelf;
}
public void setNewPlateLocationShelf(String newPlateLocationShelf)
{
_newPlateLocationShelf = newPlateLocationShelf;
}
public String getNewPlateLocationBin()
{
return _newPlateLocationBin;
}
public void setNewPlateLocationBin(String newPlateLocationBin)
{
_newPlateLocationBin = newPlateLocationBin;
}
}