Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<String> sync(IUser user, String groupId, long contentVersion, String collectionId) throws ZoteroHttpStatusException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

public interface ICitationManager {

boolean cancel(String groupId);

List<ICitationGroup> getGroups(IUser user);

CitationResults getGroupItems(IUser user, String groupId, String collectionId, int page, String sortBy, List<String> conceptIds)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -83,14 +85,14 @@ public void init() {
*/
@Override
@Async
public void sync(IUser user, String groupId, long contentVersion, String collectionId) throws ZoteroHttpStatusException {
public Future<String> 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<String>(null);
}

logger.info("Starting sync for " + groupId);
Expand All @@ -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<String>(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
Expand All @@ -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<String>(job.getId());
}

syncCitations(user, groupId, job, versions, counter);

if(checkIfThreadIsInterruptedAndCancelJob(job, groupId)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, now that I'm looking at this again, you will want to cancel the thread within the syncCitations and probably also syncCollections method. They are the long running methods that need to be cancelled if cancellation was requested. Otherwise, if there are 1000 citations to sync it would still go through all of them before cancelling.

return new AsyncResult<String>(job.getId());
}

syncCollections(user, groupId, job, collectionVersions, groupVersion, counter);

if(checkIfThreadIsInterruptedAndCancelJob(job, groupId)) {
return new AsyncResult<String>(job.getId());
}

removeDeletedItems(deletedElements, job);


if(checkIfThreadIsInterruptedAndCancelJob(job, groupId)) {
return new AsyncResult<String>(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<ICitationGroup> 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<String>(job.getId());
}

job.setStatus(JobStatus.DONE);
job.setFinishedOn(OffsetDateTime.now());
jobRepo.save(job);

return new AsyncResult<String>(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<String, Long> versions,
AtomicInteger counter) throws ZoteroHttpStatusException {
List<String> keysToRetrieve = new ArrayList<>();
for (String key : versions.keySet()) {

if (checkIfThreadIsInterruptedAndCancelJob(job, groupId)) {
return;
}

Optional<ICitation> citation = citationStore.findById(key);

if (citation.isPresent()) {
Expand Down Expand Up @@ -174,6 +224,11 @@ private void syncCollections(IUser user, String groupId, GroupSyncJob job, Map<S
keys.addAll(versions.keySet());

for (String key : keys) {

if (checkIfThreadIsInterruptedAndCancelJob(job, groupId)) {
return; // Ensure we stop processing if the job is cancelled
}

ICitationCollection collection = collectionRepo.findByKeyAndGroupId(key, groupId);
if (collection == null || (versions.containsKey(key) && collection.getVersion() != versions.get(key))
|| collection.getContentVersion() != groupVersion) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -93,6 +95,8 @@

private Map<String, BiFunction<ICitation, ICitation, Integer>> sortFunctions;

Map<String, Future<String>> futureMap = new ConcurrentHashMap<>();

@PostConstruct
public void init() {
sortFunctions = new HashMap<>();
Expand Down Expand Up @@ -380,78 +384,89 @@
throw new GroupDoesNotExistException("Group " + groupId + " does not exist.");
}

@Override
public CitationResults getGroupItems(IUser user, String groupId, String collectionId, int page, String sortBy, List<String> conceptIds)
throws GroupDoesNotExistException, ZoteroHttpStatusException {

ICitationGroup group = null;
Optional<ICitationGroup> groupOptional = groupRepository.findFirstByGroupId(new Long(groupId));
if (!groupOptional.isPresent() || !groupOptional.get().getUsers().contains(user.getUsername())) {
group = zoteroManager.getGroup(user, groupId, false);
if (group != null) {
if (groupOptional.isPresent()){
group.setId(groupOptional.get().getId());
}
group.getUsers().add(user.getUsername());
groupRepository.save((CitationGroup) group);
}
} else {
group = groupOptional.get();
}

if (group == null) {
throw new GroupDoesNotExistException("There is no group with id " + groupId);
}

boolean isModified = zoteroManager.isGroupModified(user, groupId, group.getContentVersion());
CitationResults results = new CitationResults();
if (isModified) {
long previousVersion = group.getContentVersion();
// first update the group info
// if we are using a previously stored group, delete it
ICitationGroup zoteroGroup = null;
if (group.getId() != null) {
zoteroGroup = zoteroManager.getGroup(user, groupId + "", true);
zoteroGroup.setId(group.getId());
}
zoteroGroup.setUpdatedOn(OffsetDateTime.now().toString());
addUserToGroup(zoteroGroup, user);
group = groupRepository.save((CitationGroup) zoteroGroup);

// then update content
results.setNotModified(false);
asyncCitationProcessor.sync(user, group.getGroupId() + "", previousVersion, collectionId);
Future<String> future = asyncCitationProcessor.sync(user, group.getGroupId() + "", previousVersion, collectionId);
futureMap.put(groupId, future);
} else {
results.setNotModified(true);
}

List<ICitation> citations = null;
long total = 0;
if (collectionId != null && !collectionId.trim().isEmpty()) {
citations = (List<ICitation>) citationDao.findCitationsInCollection(groupId, collectionId, (page - 1) * zoteroPageSize, zoteroPageSize, conceptIds);
ICitationCollection collection = collectionManager.getCollection(user, groupId, collectionId);
if (collection != null) {
total = collection.getNumberOfItems();
} else {
total = citations.size();
}
} else {
citations = (List<ICitation>) citationDao.findCitations(groupId, (page - 1) * zoteroPageSize,
zoteroPageSize, false, conceptIds);
if (groupOptional.isPresent()) {
updateCitationGroup(user, groupId);

total = groupRepository.findFirstByGroupId(new Long(groupId)).get().getNumItems();
} else {
total = citations.size();
}
}
results.setCitations(citations != null ? citations : new ArrayList<>());
results.setTotalResults(total);
return results;

}

Check notice on line 459 in citesphere/src/main/java/edu/asu/diging/citesphere/core/service/impl/CitationManager.java

View check run for this annotation

codefactor.io / CodeFactor

citesphere/src/main/java/edu/asu/diging/citesphere/core/service/impl/CitationManager.java#L387-L459

Complex Method
@Override
public boolean cancel(String groupId) {
Future<String> 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<ICitationGroup> groupOptional = groupRepository.findFirstByGroupId(new Long(groupId));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
*/
Expand Down Expand Up @@ -81,12 +80,11 @@ public void cancelJob(String jobId) {
Optional<GroupSyncJob> 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);
}
}
}