package uk.ac.ox.zoo.seeg.abraid.mp.common.service.core; import org.apache.commons.math3.util.Pair; import org.joda.time.DateTime; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.transaction.annotation.Transactional; import uk.ac.ox.zoo.seeg.abraid.mp.common.dao.*; import uk.ac.ox.zoo.seeg.abraid.mp.common.domain.*; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Service class for experts. * * Copyright (c) 2014 University of Oxford */ @Transactional(rollbackFor = Exception.class) public class ExpertServiceImpl implements ExpertService { private final AdminUnitReviewDao adminUnitReviewDao; private final ExpertDao expertDao; private final DiseaseGroupDao diseaseGroupDao; private final DiseaseOccurrenceDao diseaseOccurrenceDao; private final DiseaseOccurrenceReviewDao diseaseOccurrenceReviewDao; private final PasswordResetRequestDao passwordResetRequestDao; private final PasswordEncoder passwordEncoder; public ExpertServiceImpl(AdminUnitReviewDao adminUnitReviewDao, ExpertDao expertDao, DiseaseGroupDao diseaseGroupDao, DiseaseOccurrenceDao diseaseOccurrenceDao, DiseaseOccurrenceReviewDao diseaseOccurrenceReviewDao, PasswordResetRequestDao passwordResetRequestDao, PasswordEncoder passwordEncoder) { this.adminUnitReviewDao = adminUnitReviewDao; this.expertDao = expertDao; this.diseaseGroupDao = diseaseGroupDao; this.diseaseOccurrenceDao = diseaseOccurrenceDao; this.diseaseOccurrenceReviewDao = diseaseOccurrenceReviewDao; this.passwordResetRequestDao = passwordResetRequestDao; this.passwordEncoder = passwordEncoder; } /** * Gets a list of all experts. * @return A list of all experts. */ public List<Expert> getAllExperts() { return expertDao.getAll(); } /** * Gets an expert by ID. * @param expertId The ID of the expert. * @return The expert, or null if not found. */ @Override public Expert getExpertById(int expertId) { return expertDao.getById(expertId); } /** * Gets an expert by email address. * @param email The email address. * @return The expert, or null if not found. * @throws org.springframework.dao.DataAccessException if multiple experts with this email address are found * (should not occur as emails are unique) */ @Override public Expert getExpertByEmail(String email) { return expertDao.getByEmail(email); } /** * Gets a set of the specified expert's disease interests. * @param expertId The id of the specified expert. * @return The list of validator disease groups the expert can validate. */ @Override public List<ValidatorDiseaseGroup> getDiseaseInterests(Integer expertId) { Expert expert = expertDao.getById(expertId); return expert.getValidatorDiseaseGroups(); } /** * Gets a list of occurrence points, for the specified validator disease group, for which the specified expert has * not yet submitted a review. Only SEEG users may view occurrences of disease groups before first model run prep. * Other external users may only view occurrences of disease groups with automatic model runs enabled. * @param expertId The id of the specified expert. * @param userIsSeeg Whether the expert is a member of SEEG, and therefore should review more occurrences. * @param validatorDiseaseGroupId The id of the validatorDiseaseGroup of interest. * @return The list of disease occurrence points to be displayed to the expert on the map. * @throws java.lang.IllegalArgumentException if the expertId or validatorDiseaseGroupId cannot be found in the * database. */ @Override public List<DiseaseOccurrence> getDiseaseOccurrencesYetToBeReviewedByExpert(Integer expertId, boolean userIsSeeg, Integer validatorDiseaseGroupId) { return diseaseOccurrenceDao.getDiseaseOccurrencesYetToBeReviewedByExpert( expertId, userIsSeeg, validatorDiseaseGroupId); } /** * Gets the number of disease occurrence reviews an expert has submitted, across all disease groups. * @param expertId The id of the specified expert. * @return The total number of disease occurrence reviews for the specified expert. */ @Override public Long getDiseaseOccurrenceReviewCount(Integer expertId) { return diseaseOccurrenceReviewDao.getCountByExpertId(expertId); } /** * Determines whether a review for the specified disease occurrence, by the specified expert, * already exists in the database. * @param diseaseOccurrenceId The id of the disease group. * @param expertId The id of the specified expert. * @return True if the review already exists, otherwise false. */ @Override public boolean doesDiseaseOccurrenceReviewExist(Integer expertId, Integer diseaseOccurrenceId) { return diseaseOccurrenceReviewDao.doesDiseaseOccurrenceReviewExist(expertId, diseaseOccurrenceId); } /** * Determines whether a review already exists for the specified admin unit, disease group and expert. * Each expert can only submit one review per disease/admin unit for each run of the disease extent generator. * The createdDate of the expert's most recent matching review will be checked against the disease group's * lastExtentGenerationDate property. * * @param expertId The id of the specified expert. * @param diseaseGroup The disease group. * @param adminUnit The tropical or global admin unit. * @return True if the review already exists, otherwise false. */ @Override public boolean doesAdminUnitReviewExistForLatestDiseaseExtent( Integer expertId, DiseaseGroup diseaseGroup, AdminUnitGlobalOrTropical adminUnit) { DateTime lastExtentGenerationDate = diseaseGroup.getLastExtentGenerationDate(); if (lastExtentGenerationDate == null) { return false; } else { DateTime lastReviewDate = adminUnitReviewDao.getLastReviewDateByExpertIdAndDiseaseGroupIdAndGaulCode( expertId, diseaseGroup.getId(), adminUnit.getGaulCode()); return lastReviewDate != null && lastReviewDate.isAfter(diseaseGroup.getLastExtentGenerationDate()); } } /** * Gets all reviews for the specified disease group, including repeat reviews. * @param diseaseGroupId The id of the disease group. * @return A list of reviews. */ @Override public List<AdminUnitReview> getAllAdminUnitReviewsForDiseaseGroup(Integer diseaseGroupId) { return adminUnitReviewDao.getByDiseaseGroupId(diseaseGroupId); } /** * Gets the reviews for the specified disease group, excluding repeat reviews (most recent only). * @param diseaseGroupId The id of the disease group. * @return A set of reviews. */ @Override public Collection<AdminUnitReview> getCurrentAdminUnitReviewsForDiseaseGroup(Integer diseaseGroupId) { List<AdminUnitReview> allReviews = getAllAdminUnitReviewsForDiseaseGroup(diseaseGroupId); Map<Pair<Integer, Integer>, AdminUnitReview> processedReviews = new HashMap<>(); for (AdminUnitReview review : allReviews) { Pair<Integer, Integer> key = new Pair<>( review.getAdminUnitGlobalOrTropicalGaulCode(), review.getExpert().getId()); if (!processedReviews.containsKey(key) || reviewIsNewer(processedReviews.get(key), review)) { processedReviews.put(key, review); } } return processedReviews.values(); } /** * Gets all reviews submitted by the specified expert, for the specified disease group. * @param expertId The id of the specified expert. * @param diseaseGroupId The id of the disease group. * @return A list of reviews. */ @Override public List<AdminUnitReview> getAllAdminUnitReviewsForDiseaseGroup(Integer expertId, Integer diseaseGroupId) { return adminUnitReviewDao.getByExpertIdAndDiseaseGroupId(expertId, diseaseGroupId); } private static boolean reviewIsNewer(AdminUnitReview review, AdminUnitReview newReview) { return review.getCreatedDate().isBefore(newReview.getCreatedDate()); } /** * Creates a new PasswordResetRequest entry in the database. This will also delete any other PasswordResetRequest * for this user and any PasswordResetRequests older than 24 hours. * @param email The email address of the associated expert. * @param key The key used to secure the PasswordResetRequest. * @return The ID of the new PasswordResetRequest. */ @Override public Integer createAndSavePasswordResetRequest(String email, String key) { passwordResetRequestDao.removeOldRequests(); Expert expert = expertDao.getByEmail(email); String hashedKey = passwordEncoder.encode(key); PasswordResetRequest passwordResetRequest = new PasswordResetRequest(expert, hashedKey); passwordResetRequestDao.removeRequestsIssuedForExpert(expert); passwordResetRequestDao.save(passwordResetRequest); return passwordResetRequest.getId(); } /** * Gets a PasswordResetRequest by id. * This will also delete any PasswordResetRequests older than 24 hours. * @param id The id. * @return The PasswordResetRequest. */ @Override public PasswordResetRequest getPasswordResetRequest(Integer id) { passwordResetRequestDao.removeOldRequests(); if (id == null) { return null; } return passwordResetRequestDao.getById(id); } /** * Verifies a PasswordResetRequest key against the stored key hash for a PasswordResetRequest identified by its id. * This will also delete any PasswordResetRequests older than 24 hours. * @param id The id of the PasswordResetRequest to check against. * @param key The key to check. * @return true if the check was successful. */ @Override public boolean checkPasswordResetRequest(Integer id, String key) { passwordResetRequestDao.removeOldRequests(); PasswordResetRequest passwordResetRequest = passwordResetRequestDao.getById(id); return passwordResetRequest != null && passwordEncoder.matches(key, passwordResetRequest.getHashedKey()); } /** * Deletes a PasswordResetRequest. * This will also delete any PasswordResetRequests older than 24 hours. * @param passwordResetRequest The PasswordResetRequest. */ @Override public void deletePasswordResetRequest(PasswordResetRequest passwordResetRequest) { passwordResetRequestDao.removeOldRequests(); passwordResetRequestDao.delete(passwordResetRequest); } /** * Gets the number of admin unit reviews an expert has submitted, across all disease groups. * @param expertId The id of the specified expert. * @return The total number of admin unit reviews for the specified expert. */ @Override public Long getAdminUnitReviewCount(Integer expertId) { return adminUnitReviewDao.getCountByExpertId(expertId); } /** * Gets a page worth of publicly visible experts. * @param pageNumber The page number to return. * @param pageSize The size of the pages to split the visible experts into. * @return A page worth of publicly visible experts */ @Override public List<Expert> getPageOfPubliclyVisibleExperts(int pageNumber, int pageSize) { return expertDao.getPageOfPubliclyVisible(pageNumber, pageSize); } /** * Gets a count of the publicly visible experts. * @return The count. */ @Override public long getCountOfPubliclyVisibleExperts() { return expertDao.getCountOfPubliclyVisible(); } /** * Gets the date of the last review submitted by a specific expert. * @param expertId The expert's Id. * @return The date of the last review. */ @Override public DateTime getLastReviewDate(Integer expertId) { final DateTime lastAdminUnitReviewDate = adminUnitReviewDao.getLastReviewDateByExpertId(expertId); final DateTime lastOccurrenceReviewDate = diseaseOccurrenceReviewDao.getLastReviewDateByExpertId(expertId); if (lastAdminUnitReviewDate == null) { return lastOccurrenceReviewDate; } else if (lastOccurrenceReviewDate == null) { return lastAdminUnitReviewDate; } else if (lastAdminUnitReviewDate.isAfter(lastOccurrenceReviewDate)) { return lastAdminUnitReviewDate; } else { return lastOccurrenceReviewDate; } } /** * Saves the disease occurrence review. * @param expertId The id of the expert providing review. * @param occurrenceId The id of the disease occurrence. * @param response The expert's response. */ @Override public void saveDiseaseOccurrenceReview(Integer expertId, Integer occurrenceId, DiseaseOccurrenceReviewResponse response) { Expert expert = getExpertById(expertId); DiseaseOccurrence diseaseOccurrence = diseaseOccurrenceDao.getById(occurrenceId); DiseaseOccurrenceReview review = new DiseaseOccurrenceReview(expert, diseaseOccurrence, response); diseaseOccurrenceReviewDao.save(review); } /** * Saves the review of the administrative unit. * @param expertId The id of the expert providing review. * @param diseaseGroupId The id of the disease group. * @param gaulCode The gaulCode of the administrative unit. * @param response The expert's response. */ @Override public void saveAdminUnitReview(Integer expertId, Integer diseaseGroupId, Integer gaulCode, DiseaseExtentClass response) { Expert expert = getExpertById(expertId); DiseaseGroup diseaseGroup = diseaseGroupDao.getById(diseaseGroupId); AdminUnitReview review; if (diseaseGroup.isGlobal()) { review = new AdminUnitReview(expert, gaulCode, null, diseaseGroup, response); } else { review = new AdminUnitReview(expert, null, gaulCode, diseaseGroup, response); } adminUnitReviewDao.save(review); } /** * Saves the specified expert. * @param expert The expert to save. */ @Override public void saveExpert(Expert expert) { expertDao.save(expert); } }