// $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.iccbl.screensaver.service.screens; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.SortedSet; 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.db.GenericEntityDAO; import edu.harvard.med.screensaver.model.activities.AdministrativeActivity; import edu.harvard.med.screensaver.model.screens.LibraryScreening; import edu.harvard.med.screensaver.model.screens.Screen; import edu.harvard.med.screensaver.model.screens.ScreenDataSharingLevel; import edu.harvard.med.screensaver.model.screens.ScreenStatus; import edu.harvard.med.screensaver.model.screens.ScreenType; 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.OperationRestrictedException; import edu.harvard.med.screensaver.service.ServiceMessages; import edu.harvard.med.screensaver.util.Pair; /** * The Screen Expiration Service is used to adjust the DataSharingLevel of the Screen. Screens that are more restrictive have * the ScreenDataSharingLevel of "shared" or "mutual" sharing levels. These more restrictive Screens are subject expiration of * this privacy to the "mutual" level after an expiration period. <br> * The expiration period is calculated as the "ageToExpireFromActivityDateInDays" days since the last Screening (Lab) Activity * for a Screen. * <ul> * <li>The calculated expiration date will be stored in the Screen.dataPrivacyExpirationDate (DPED). The DPED is set * independently, using this updater, from a batch process. * <li>Independently, other batch process will use this service: * <li>to query for Screens that will be expiring in a certain period, in order to notify concerned parties * <li>to query for Screens that have had a publication (as this is an indicator that the privacy should be manually expired). * <li>to expire those Screens that should expire, as indicated by a DPED that has passed. * @author sde4 */ public class ScreenDataSharingLevelUpdater { private static Logger log = Logger.getLogger(ScreenDataSharingLevelUpdater.class); private GenericEntityDAO _dao; private ServiceMessages _messages; private boolean testMode = false; protected ScreenDataSharingLevelUpdater() {} public ScreenDataSharingLevelUpdater(GenericEntityDAO dao, ServiceMessages serviceMessages) { _dao = dao; _messages = serviceMessages; } @Transactional public AdministrativeActivity updateScreen(Screen screen, ScreenDataSharingLevel screenDataSharingLevel, AdministratorUser recordedBy) { recordedBy = _dao.reloadEntity(recordedBy); verifyOperationPermitted(recordedBy); ScreenDataSharingLevel oldLevel = screen.getDataSharingLevel(); screen.setDataSharingLevel(screenDataSharingLevel); return screen.createUpdateActivity(recordedBy, "Data Sharing Level updated from : " + oldLevel + " to " + screenDataSharingLevel); } /** * Returns a unique list of Small Molecule Screens that: * <ul> * <li>have ScreenResults * <li>have ScreenDataSharingLevel that is more restrictive than {@link ScreenDataSharingLevel#MUTUAL_SCREENS} * <li>have a {@link Screen#getDataPrivacyExpirationDate()} on or before the expireDate * <li>do not have a status of {@link ScreenStatus#DROPPED_TECHNICAL} or * {@link ScreenStatus#TRANSFERRED_TO_BROAD_INSTITUTE} * * @param expireDate * @return not null, empty Set if nothing is found */ public List<Screen> findNewExpiredNotNotified(LocalDate expireDate, ScreenType screenType) { String hql = "select distinct(s) from Screen s " + " join s.screenResult sr " + " where" + " s.dataPrivacyExpirationDate <= ? " + " and s.dataSharingLevel > ? " + " and s.screenType = ? " + " and s.dataPrivacyExpirationNotifiedDate is null" + // " and s.screenId not in (select s.screenId from Screen s join s.statusItems si where si.status in ( ?, ? ) and si.screen = s ) " " and s.screenId not in (select s2.screenId from Screen s2 join s2.statusItems si where si.status in ( ?, ? ) and s2=s ) " + " order by s.screenId"; List<Screen> list = _dao.findEntitiesByHql(Screen.class, hql, expireDate, ScreenDataSharingLevel.MUTUAL_SCREENS, screenType, ScreenStatus.DROPPED_TECHNICAL, ScreenStatus.TRANSFERRED_TO_BROAD_INSTITUTE); log.info("Hql: " + hql + ", " + list.size()); return list; } public List<Screen> findExpired(LocalDate expireDate, ScreenType screenType) { String hql = "select distinct(s) from Screen s " + " join s.screenResult sr " + " where" + " s.dataPrivacyExpirationDate <= ? " + " and s.dataSharingLevel > ? " + " and s.screenType = ? " + " and s.screenId not in (select s2.screenId from Screen s2 join s2.statusItems si where si.status in ( ?, ? ) and s2=s ) " + " order by s.screenId"; List<Screen> list = _dao.findEntitiesByHql(Screen.class, hql, expireDate, ScreenDataSharingLevel.MUTUAL_SCREENS, screenType , ScreenStatus.DROPPED_TECHNICAL, ScreenStatus.TRANSFERRED_TO_BROAD_INSTITUTE ); log.info("Hql: " + hql + ", " + list.size()); return list; } /** * For Screens that: * <ul> * <li>have ScreenResults * <li>have ScreenDataSharingLevel that is more restrictive than {@link ScreenDataSharingLevel#MUTUAL_SCREENS} * <li>have a {@link Screen#getDataPrivacyExpirationDate()} on or before the expireDate * <li>do not have a status of {@link ScreenStatus#DROPPED_TECHNICAL} or {@link ScreenStatus#TRANSFERRED_TO_BROAD_INSTITUTE} * </ul> * Expire any Screen returned from {@link ScreenDataSharingLevelUpdater#findNewExpiredNotNotified(LocalDate)} wherein the * {@link Screen#getDataPrivacyExpirationDate()} is less than or equal to the passed in date. * @param date * @return Screens updated */ @Transactional public List<Pair<Screen,AdministrativeActivity>> expireScreenDataSharingLevels(LocalDate date, AdministratorUser recordedBy, ScreenType screenType) { verifyOperationPermitted(recordedBy); List<Screen> screens = findExpired(date, screenType); List<Pair<Screen,AdministrativeActivity>> results = Lists.newLinkedList(); for(Screen screen:screens) { assert(screen.getDataPrivacyExpirationDate().compareTo(date) <= 0); AdministrativeActivity activity = updateScreen(screen, ScreenDataSharingLevel.MUTUAL_SCREENS, recordedBy); results.add(new Pair<Screen,AdministrativeActivity>(screen,activity)); } return results; } public static class DataPrivacyAdjustment { public boolean isEmpty(boolean considerOverrides) { return (screensAdjusted.isEmpty() && screensAdjustedToAllowed.isEmpty() && !considerOverrides) || ( screensAdjusted.isEmpty() && screensAdjustedToAllowed.isEmpty() && considerOverrides && screenPrivacyAdjustmentNotAllowed.isEmpty() ); } public List<Pair<Screen,AdministrativeActivity>> screensAdjusted = Lists.newLinkedList(); public List<Pair<Screen,AdministrativeActivity>> screensAdjustedToAllowed = Lists.newLinkedList(); public List<Pair<Screen,String>> screenPrivacyAdjustmentNotAllowed = Lists.newLinkedList(); } /** * For Screens that: * <ul> * <li>have ScreenResults * <li>have ScreenDataSharingLevel that is more restrictive than {@link ScreenDataSharingLevel#MUTUAL_SCREENS} * <li>have a {@link Screen#getDataPrivacyExpirationDate()} on or before the expireDate * <li>do not have a status of {@link ScreenStatus#DROPPED_TECHNICAL} or {@link ScreenStatus#TRANSFERRED_TO_BROAD_INSTITUTE} * </ul> * This method invokes {@link Screen#setDataPrivacyExpirationDate(LocalDate)} with a value that is ageToExpireFromActivityDateInDays * days from the latest LabActivity (by date) for the Screen. * @param ageToExpireFromActivityDateInDays the expiration period, in days * @param admin user performing * @return List<Screen, Pair<Admin-activity-if-any, message-if-no-action> > */ @Transactional public DataPrivacyAdjustment adjustDataPrivacyExpirationByActivities(int ageToExpireFromActivityDateInDays, AdministratorUser admin, ScreenType screenType) { admin = _dao.reloadEntity(admin); String hql = "select distinct(s) from Screen as s " + " join s.screenResult sr " + " inner join s.labActivities la " + " where " + " s.screenType = ? " + " and s.dataSharingLevel > ? " + " and s.screenId not in (select s2.screenId from Screen s2 join s2.statusItems si where si.status in ( ?, ? ) and s2=s ) "+ " order by s.screenId"; List<Screen> screens = _dao.findEntitiesByHql(Screen.class, hql, screenType, ScreenDataSharingLevel.MUTUAL_SCREENS, ScreenStatus.DROPPED_TECHNICAL, ScreenStatus.TRANSFERRED_TO_BROAD_INSTITUTE); // Note, have to iterate in code to examine the date values as a suitable date arithmetic method was not determined for the HQL - sde4 DataPrivacyAdjustment dataPrivacyAdjustment = new DataPrivacyAdjustment(); for(Screen s:screens) { // note that getLabActivities does a "natural" sort, and that the comparator for the LabActivity uses the dateOfActivity to sort. SortedSet<LibraryScreening> libraryScreenings = s.getLabActivitiesOfType(LibraryScreening.class); if(libraryScreenings.isEmpty()) { log.debug("No LibraryScreenings for the screen: " + s); } else { //for [#2285] - don't consider Library Screenings of user provided plates for(Iterator<LibraryScreening> iter = libraryScreenings.iterator(); iter.hasNext();) { if(iter.next().isForExternalLibraryPlates()) iter.remove(); } LibraryScreening libraryScreening = libraryScreenings.last(); LocalDate requestedDate = libraryScreening.getDateOfActivity().plusDays(ageToExpireFromActivityDateInDays); LocalDate currentExpiration = s.getDataPrivacyExpirationDate(); if( currentExpiration != null && currentExpiration.equals(requestedDate) ) { log.info("DataPrivacyExpirationDate is already set to the correct value for screen number: " + s.getFacilityId()); } else { s.setDataPrivacyExpirationDate(requestedDate); LocalDate setDate = s.getDataPrivacyExpirationDate(); AdministrativeActivity adminActivity= null; if(currentExpiration != null && currentExpiration.equals(setDate)) { // adjustment not allowed - overridden String msg = _messages.getMessage("admin.screens.dataPrivacyExpiration.dataPrivacyExpirationdate.adjustment.adjustmentNotAllowed.comment", currentExpiration, libraryScreening.getDateOfActivity(), requestedDate); dataPrivacyAdjustment.screenPrivacyAdjustmentNotAllowed.add(Pair.newPair(s, msg)); } else { if(! requestedDate.equals(setDate)) { // adjustment allowed - but overridden to... String msg = _messages.getMessage("admin.screens.dataPrivacyExpiration.dataPrivacyExpirationdate.adjustment.adjustedBasedOnAllowed.comment", currentExpiration, libraryScreening.getDateOfActivity(), requestedDate, setDate); adminActivity = s.createUpdateActivity(admin,msg); dataPrivacyAdjustment.screensAdjustedToAllowed.add(Pair.newPair(s,adminActivity)); } else { // adjustment allowed String msg = _messages.getMessage("admin.screens.dataPrivacyExpiration.dataPrivacyExpirationdate.adjustment.basedOnActivity.comment", currentExpiration, libraryScreening.getDateOfActivity(), setDate); adminActivity = s.createUpdateActivity(admin,msg); dataPrivacyAdjustment.screensAdjusted.add(Pair.newPair(s,adminActivity)); } // we null the notified date, since it should be re-notified -sde4 s.setDataPrivacyExpirationNotifiedDate(null); } } } } log.info("adjustDataPrivacyExpirationByActivities, " + " adjusted: " + dataPrivacyAdjustment.screensAdjusted.size() + " adjusted to allowed: " + dataPrivacyAdjustment.screensAdjustedToAllowed.size() + ", adjustment not allowed: " + dataPrivacyAdjustment.screenPrivacyAdjustmentNotAllowed.size() ); return dataPrivacyAdjustment; } /** * Find Screens that: * <ul> * <li>have Publications * <li>have ScreenResults * <li>have ScreenDataSharingLevel that is more restrictive than {@link ScreenDataSharingLevel#MUTUAL_SCREENS} * <li>have a {@link Screen#getDataPrivacyExpirationDate()} on or before the expireDate * <li>do not have a status of {@link ScreenStatus#DROPPED_TECHNICAL} or {@link ScreenStatus#TRANSFERRED_TO_BROAD_INSTITUTE} * </ul> */ public List<Screen> findNewPublishedPrivate(ScreenType screenType) { String hql = "select distinct (s) from Screen as s " + " join s.publications " + " join s.screenResult sr " + " where s.dataSharingLevel > ? " + " and s.screenType = ? " + " and s.screenId not in (select s2.screenId from Screen s2 join s2.statusItems si where si.status in ( ?, ? ) and s2=s ) " + " order by s.screenId"; return _dao.findEntitiesByHql(Screen.class, hql, ScreenDataSharingLevel.SHARED, screenType, ScreenStatus.DROPPED_TECHNICAL, ScreenStatus.TRANSFERRED_TO_BROAD_INSTITUTE); } @Transactional public void setDataPrivacyExpirationNotifiedDate(Screen screen) { screen.setDataPrivacyExpirationNotifiedDate(new LocalDate()); _dao.saveOrUpdateEntity(screen); } private void verifyOperationPermitted(AdministratorUser recordedBy) throws OperationRestrictedException { if (!!!recordedBy.getScreensaverUserRoles().contains(ScreensaverUserRole.SCREEN_DATA_SHARING_LEVELS_ADMIN)) { throw new OperationRestrictedException("to update a Screen data sharing level, administrator must have " + ScreensaverUserRole.SCREEN_DATA_SHARING_LEVELS_ADMIN.getDisplayableRoleName() + " role"); } } public Set<ScreensaverUser> findDataSharingLevelAdminUsers() { String hql = "from ScreensaverUser where ? in elements (screensaverUserRoles)" ; return Sets.newHashSet(_dao.findEntitiesByHql(ScreensaverUser.class, hql, ScreensaverUserRole.SCREEN_DSL_EXPIRATION_NOTIFY.getRoleName())); } }