diff --git a/citesphere/src/main/java/edu/asu/diging/citesphere/core/service/IAsyncCitationProcessor.java b/citesphere/src/main/java/edu/asu/diging/citesphere/core/service/IAsyncCitationProcessor.java index d1782688c..130aaa3f3 100644 --- a/citesphere/src/main/java/edu/asu/diging/citesphere/core/service/IAsyncCitationProcessor.java +++ b/citesphere/src/main/java/edu/asu/diging/citesphere/core/service/IAsyncCitationProcessor.java @@ -1,10 +1,12 @@ package edu.asu.diging.citesphere.core.service; +import java.util.concurrent.Future; + import edu.asu.diging.citesphere.core.exceptions.ZoteroHttpStatusException; import edu.asu.diging.citesphere.user.IUser; public interface IAsyncCitationProcessor { - void sync(IUser user, String groupId, long contentVersion, String collectionId) throws ZoteroHttpStatusException; + Future sync(IUser user, String groupId, long contentVersion, String collectionId) throws ZoteroHttpStatusException; } \ No newline at end of file diff --git a/citesphere/src/main/java/edu/asu/diging/citesphere/core/service/ICitationManager.java b/citesphere/src/main/java/edu/asu/diging/citesphere/core/service/ICitationManager.java index e434356ad..2022f8f9d 100644 --- a/citesphere/src/main/java/edu/asu/diging/citesphere/core/service/ICitationManager.java +++ b/citesphere/src/main/java/edu/asu/diging/citesphere/core/service/ICitationManager.java @@ -29,6 +29,8 @@ public interface ICitationManager { + boolean cancel(String groupId); + List getGroups(IUser user); CitationResults getGroupItems(IUser user, String groupId, String collectionId, int page, String sortBy, List conceptIds) diff --git a/citesphere/src/main/java/edu/asu/diging/citesphere/core/service/impl/AsyncCitationProcessor.java b/citesphere/src/main/java/edu/asu/diging/citesphere/core/service/impl/AsyncCitationProcessor.java index 998820267..f7bd6146b 100644 --- a/citesphere/src/main/java/edu/asu/diging/citesphere/core/service/impl/AsyncCitationProcessor.java +++ b/citesphere/src/main/java/edu/asu/diging/citesphere/core/service/impl/AsyncCitationProcessor.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -17,6 +18,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.stereotype.Service; import edu.asu.diging.citesphere.core.exceptions.ZoteroHttpStatusException; @@ -83,14 +85,14 @@ public void init() { */ @Override @Async - public void sync(IUser user, String groupId, long contentVersion, String collectionId) throws ZoteroHttpStatusException { + public Future sync(IUser user, String groupId, long contentVersion, String collectionId) throws ZoteroHttpStatusException { GroupSyncJob prevJob = jobManager.getMostRecentJob(groupId + ""); // it's un-intuitive to test for not inactive statuses here, but it's more likely we'll add - // more activate job statuses than inactive ones, so it's less error prone to use the list that + // more active job statuses than inactive ones, so it's less error prone to use the list that // is less likely to change. if (prevJob != null && !inactiveJobStatuses.contains(prevJob.getStatus())) { // there is already a job running, let's not start another one - return; + return new AsyncResult(null); } logger.info("Starting sync for " + groupId); @@ -101,6 +103,10 @@ public void sync(IUser user, String groupId, long contentVersion, String collect jobRepo.save(job); jobManager.addJob(job); + if(checkIfThreadIsInterruptedAndCancelJob(job, groupId)) { + return new AsyncResult(job.getId()); + } + // we'll retrieve the latest group version first in case there are more changes // in between // this way the group version can be out-dated and trigger another sync next @@ -119,30 +125,74 @@ public void sync(IUser user, String groupId, long contentVersion, String collect jobRepo.save(job); AtomicInteger counter = new AtomicInteger(); + + if(checkIfThreadIsInterruptedAndCancelJob(job, groupId)) { + return new AsyncResult(job.getId()); + } + syncCitations(user, groupId, job, versions, counter); + + if(checkIfThreadIsInterruptedAndCancelJob(job, groupId)) { + return new AsyncResult(job.getId()); + } + syncCollections(user, groupId, job, collectionVersions, groupVersion, counter); + if(checkIfThreadIsInterruptedAndCancelJob(job, groupId)) { + return new AsyncResult(job.getId()); + } + removeDeletedItems(deletedElements, job); - + if(checkIfThreadIsInterruptedAndCancelJob(job, groupId)) { + return new AsyncResult(job.getId()); + } + // while this thread has been running, the group might have been updated by another thread // so, we have to make sure there is no group with the same group id but other object id // or we'll end up with two groups with the same group id. Optional group = groupRepo.findFirstByGroupId(new Long(groupId)); + if (group.isPresent()) { group.get().setContentVersion(groupVersion); groupRepo.save((CitationGroup) group.get()); } + if(checkIfThreadIsInterruptedAndCancelJob(job, groupId)) { + return new AsyncResult(job.getId()); + } + job.setStatus(JobStatus.DONE); job.setFinishedOn(OffsetDateTime.now()); jobRepo.save(job); + + return new AsyncResult(job.getId()); + } + + private boolean checkIfThreadIsInterruptedAndCancelJob(GroupSyncJob job, String groupId) { + if(Thread.currentThread().isInterrupted()) { + setJobToCanceledState(job, groupId); + return true; + } + return false; + } + + private void setJobToCanceledState(GroupSyncJob job, String groupId) { + logger.info("Aborting sync for " + groupId); + job.setStatus(JobStatus.CANCELED); + job.setFinishedOn(OffsetDateTime.now()); + jobRepo.save(job); } private void syncCitations(IUser user, String groupId, GroupSyncJob job, Map versions, AtomicInteger counter) throws ZoteroHttpStatusException { List keysToRetrieve = new ArrayList<>(); for (String key : versions.keySet()) { + + if (checkIfThreadIsInterruptedAndCancelJob(job, groupId)) { + return; + } + Optional citation = citationStore.findById(key); if (citation.isPresent()) { @@ -174,6 +224,11 @@ private void syncCollections(IUser user, String groupId, GroupSyncJob job, Map> sortFunctions; + Map> futureMap = new ConcurrentHashMap<>(); + @PostConstruct public void init() { sortFunctions = new HashMap<>(); @@ -420,7 +424,8 @@ public CitationResults getGroupItems(IUser user, String groupId, String collecti // then update content results.setNotModified(false); - asyncCitationProcessor.sync(user, group.getGroupId() + "", previousVersion, collectionId); + Future future = asyncCitationProcessor.sync(user, group.getGroupId() + "", previousVersion, collectionId); + futureMap.put(groupId, future); } else { results.setNotModified(true); } @@ -452,6 +457,16 @@ public CitationResults getGroupItems(IUser user, String groupId, String collecti } + @Override + public boolean cancel(String groupId) { + Future future = futureMap.get(groupId); + if (future != null) { + futureMap.remove(groupId); + return future.cancel(true); + } + return false; + } + @Override public void forceGroupItemsRefresh(IUser user, String groupId, String collectionId, int page, String sortBy) { Optional groupOptional = groupRepository.findFirstByGroupId(new Long(groupId)); diff --git a/citesphere/src/main/java/edu/asu/diging/citesphere/core/service/jobs/impl/SyncJobManager.java b/citesphere/src/main/java/edu/asu/diging/citesphere/core/service/jobs/impl/SyncJobManager.java index fa4fb77df..62ca1f4c1 100644 --- a/citesphere/src/main/java/edu/asu/diging/citesphere/core/service/jobs/impl/SyncJobManager.java +++ b/citesphere/src/main/java/edu/asu/diging/citesphere/core/service/jobs/impl/SyncJobManager.java @@ -13,7 +13,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import edu.asu.diging.citesphere.core.model.jobs.IJob; import edu.asu.diging.citesphere.core.model.jobs.JobStatus; import edu.asu.diging.citesphere.core.model.jobs.impl.GroupSyncJob; import edu.asu.diging.citesphere.core.repository.jobs.GroupSyncJobRepository; @@ -37,7 +36,7 @@ public class SyncJobManager implements ISyncJobManager { public void init() { currentJobs = new ConcurrentHashMap<>(); } - + /* (non-Javadoc) * @see edu.asu.diging.citesphere.core.service.jobs.impl.ISyncJobManager#addJobId(edu.asu.diging.citesphere.core.model.jobs.impl.GroupSyncJob) */ @@ -81,12 +80,11 @@ public void cancelJob(String jobId) { Optional jobOptional = jobRepo.findById(jobId); if (jobOptional.isPresent()) { GroupSyncJob job = currentJobs.get(jobOptional.get().getGroupId()); - if (job == null) { - job = jobOptional.get(); + if(citationManager.cancel(job.getGroupId())) { + job.setStatus(JobStatus.CANCELED); + job.setFinishedOn(OffsetDateTime.now()); + jobRepo.save(job); } - job.setStatus(JobStatus.CANCELED); - job.setFinishedOn(OffsetDateTime.now()); - jobRepo.save(job); } } }