From c3fc780dcd3e020875209ddbd339ea0a49f4959f Mon Sep 17 00:00:00 2001 From: Matt Storer Date: Tue, 30 Jul 2024 14:11:07 -0700 Subject: [PATCH] added a scheduled task (to occur at the top of every hour) that iterates over registered workspaces and attempts to retrieve the Patient resource for each. if the backing access token is valid, the call will succeed; otherwise, an AuthenticationException will be thrown, and caught, which will trigger the shutdown and removal of the expired workspace. for #217 --- .../edu/ohsu/cmp/coach/COACHApplication.java | 5 ++- .../coach/model/omron/RefreshTokenJob.java | 27 +++++++----- .../cmp/coach/workspace/UserWorkspace.java | 1 + .../coach/workspace/UserWorkspaceService.java | 44 ++++++++++++++++++- 4 files changed, 63 insertions(+), 14 deletions(-) diff --git a/src/main/java/edu/ohsu/cmp/coach/COACHApplication.java b/src/main/java/edu/ohsu/cmp/coach/COACHApplication.java index e9c298ad..a281299a 100644 --- a/src/main/java/edu/ohsu/cmp/coach/COACHApplication.java +++ b/src/main/java/edu/ohsu/cmp/coach/COACHApplication.java @@ -1,12 +1,13 @@ package edu.ohsu.cmp.coach; +import edu.ohsu.cmp.coach.config.RedcapConfigurationValidator; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; - -import edu.ohsu.cmp.coach.config.RedcapConfigurationValidator; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableScheduling public class COACHApplication { public static void main(String[] args) { SpringApplication.run(COACHApplication.class, args); diff --git a/src/main/java/edu/ohsu/cmp/coach/model/omron/RefreshTokenJob.java b/src/main/java/edu/ohsu/cmp/coach/model/omron/RefreshTokenJob.java index 2892af54..6d561fd9 100644 --- a/src/main/java/edu/ohsu/cmp/coach/model/omron/RefreshTokenJob.java +++ b/src/main/java/edu/ohsu/cmp/coach/model/omron/RefreshTokenJob.java @@ -32,19 +32,24 @@ public void execute(JobExecutionContext jobExecutionContext) throws JobExecution String refreshToken = jobDataMap.getString(JOBDATA_REFRESHTOKEN); UserWorkspaceService userWorkspaceService = ctx.getBean(UserWorkspaceService.class); - OmronService omronService = ctx.getBean(OmronService.class); - - try { - RefreshTokenResponse response = omronService.refreshAccessToken(refreshToken); - if (response != null) { - UserWorkspace workspace = userWorkspaceService.get(sessionId); - workspace.getOmronTokenData().update(response); - omronService.scheduleAccessTokenRefresh(sessionId); + if (userWorkspaceService.exists(sessionId)) { + OmronService omronService = ctx.getBean(OmronService.class); + + try { + RefreshTokenResponse response = omronService.refreshAccessToken(refreshToken); + if (response != null) { + UserWorkspace workspace = userWorkspaceService.get(sessionId); + workspace.getOmronTokenData().update(response); + omronService.scheduleAccessTokenRefresh(sessionId); + } + + } catch (Exception e) { + throw new JobExecutionException("caught " + e.getClass().getName() + " executing job for session=" + + sessionId + " - " + e.getMessage(), e); } - } catch (Exception e) { - throw new JobExecutionException("caught " + e.getClass().getName() + " executing job for session=" + - sessionId + " - " + e.getMessage(), e); + } else { + logger.info("aborting job {} - user workspace for session={} does not exist", name, sessionId); } } } diff --git a/src/main/java/edu/ohsu/cmp/coach/workspace/UserWorkspace.java b/src/main/java/edu/ohsu/cmp/coach/workspace/UserWorkspace.java index b5503e14..5bd999bd 100644 --- a/src/main/java/edu/ohsu/cmp/coach/workspace/UserWorkspace.java +++ b/src/main/java/edu/ohsu/cmp/coach/workspace/UserWorkspace.java @@ -224,6 +224,7 @@ public void clearVitalsCaches() { } public void shutdown() { + logger.info("shutting down workspace for session=" + sessionId); executorService.shutdown(); clearCaches(); diff --git a/src/main/java/edu/ohsu/cmp/coach/workspace/UserWorkspaceService.java b/src/main/java/edu/ohsu/cmp/coach/workspace/UserWorkspaceService.java index 714a1e17..e1beeb61 100644 --- a/src/main/java/edu/ohsu/cmp/coach/workspace/UserWorkspaceService.java +++ b/src/main/java/edu/ohsu/cmp/coach/workspace/UserWorkspaceService.java @@ -1,23 +1,30 @@ package edu.ohsu.cmp.coach.workspace; +import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; import edu.ohsu.cmp.coach.entity.RandomizationGroup; import edu.ohsu.cmp.coach.exception.ConfigurationException; import edu.ohsu.cmp.coach.exception.SessionMissingException; import edu.ohsu.cmp.coach.fhir.FhirConfigManager; import edu.ohsu.cmp.coach.fhir.FhirQueryManager; import edu.ohsu.cmp.coach.fhir.transform.VendorTransformer; +import edu.ohsu.cmp.coach.model.Audience; +import edu.ohsu.cmp.coach.model.AuditLevel; import edu.ohsu.cmp.coach.model.MyOmronTokenData; import edu.ohsu.cmp.coach.model.fhir.FHIRCredentialsWithClient; -import edu.ohsu.cmp.coach.model.Audience; +import edu.ohsu.cmp.coach.service.AuditService; +import edu.ohsu.cmp.coach.service.EHRService; import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.r4.model.Patient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.lang.reflect.InvocationTargetException; +import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -43,6 +50,41 @@ public UserWorkspaceService() { map = new ConcurrentHashMap<>(); } + @Scheduled(cron = "0 0 * * * *") // top of every hour, every day + public void shutdownExpiredWorkspaces() { + if (map.isEmpty()) return; + + if (map.size() == 1) logger.info("checking for expired workspaces (1 workspace registered) -"); + else logger.info("checking for expired workspaces (" + map.size() + " workspaces registered) -"); + + EHRService ehrService = ctx.getBean(EHRService.class); + AuditService auditService = ctx.getBean(AuditService.class); + + Iterator> iter = map.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + String sessionId = entry.getKey(); + try { + // if we can get the Patient resource, the access token is still valid, and the workspace should persist + Patient p = ehrService.getPatient(sessionId); + logger.debug("successfully retrieved Patient resource with id=" + p.getId() + " for session=" + sessionId + " - workspace is valid"); + + } catch (AuthenticationException ae) { + logger.info("credentials expired for session " + sessionId + " - shutting down associated workspace -"); + auditService.doAudit(sessionId, AuditLevel.INFO, "session expired", sessionId); + UserWorkspace workspace = entry.getValue(); + workspace.shutdown(); + iter.remove(); + + } catch (Exception e) { + logger.error("caught " + e.getClass().getName() + " retrieving Patient resource for session=" + sessionId + " - " + e.getMessage(), e); + } + } + + if (map.size() == 1) logger.info("done. (1 workspace remains)"); + else logger.info("done. (" + map.size() + " workspaces remain)"); + } + public boolean exists(String sessionId) { return map.containsKey(sessionId); }