/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see http://www.gnu.org/licenses/
*/
package org.phenotips.variantStoreIntegration.internal;
import org.phenotips.data.Patient;
import org.phenotips.data.PatientRepository;
import org.phenotips.data.permissions.PermissionsManager;
import org.phenotips.data.permissions.Visibility;
import org.phenotips.variantStoreIntegration.VCFUploadManager;
import org.phenotips.variantStoreIntegration.VariantStoreService;
import org.phenotips.variantStoreIntegration.events.VCFRemovalCompleteEvent;
import org.phenotips.variantStoreIntegration.events.VCFUploadCompleteEvent;
import org.phenotips.variantStoreIntegration.internal.jobs.FutureManager;
import org.phenotips.variantStoreIntegration.internal.jobs.VCFRemovalJob;
import org.phenotips.variantStoreIntegration.internal.jobs.VCFUploadJob;
import org.phenotips.variantstore.shared.VariantStoreException;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.component.phase.InitializationException;
import org.xwiki.context.concurrent.ExecutionContextRunnable;
import org.xwiki.observation.ObservationManager;
import java.io.File;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.slf4j.Logger;
import com.xpn.xwiki.XWikiContext;
/**
* A PhenoTips component to manage the uploading of VCF files to the variant store.
*
* @version $Id: e8350cf69218585cec6cd3267e38c5e8bbb23675 $
*/
@Component
@Singleton
public class DefaultVCFUploadManager implements VCFUploadManager
{
@Inject
private static PermissionsManager permissions;
@Inject
@Named("hidden")
private static Visibility hiddenVisibility;
@Inject
private Logger logger;
@Inject
private VariantStoreService varStore;
/** Provides access to the current execution context. */
@Inject
private Provider<XWikiContext> contextProvider;
@Inject
private ObservationManager observationManager;
@Inject
private PatientRepository pr;
@Inject
private ComponentManager componentManager;
private ExecutorService executor;
private FutureManager currentUploads;
private FutureManager currentRemovals;
@Override
public void initialize() throws InitializationException
{
this.executor = Executors.newCachedThreadPool();
this.currentUploads = new FutureManager("currentUploads", new VCFUploadCompleteEvent(null));
this.currentRemovals = new FutureManager("currentRemovals", new VCFRemovalCompleteEvent(null));
this.observationManager.addListener(this.currentUploads);
this.observationManager.addListener(this.currentRemovals);
}
/**
* {@inheritDoc}
*
*/
@Override
public void uploadVCF(String patientID, String filePath) throws Exception
{
Patient patient = this.pr.get(patientID);
if (patient == null) {
this.logger.warn("No patient found with the id: {}", patientID);
throw new Exception("Could not find the patient with ID: " + patientID);
}
File vcfFile = new File(filePath);
if (!vcfFile.exists() || !vcfFile.isFile()) {
this.logger.warn("Attempted to upload an invalid VCF file");
throw new Exception("No file was found at: " + filePath);
}
if (this.currentUploads.get(patientID) != null) {
this.logger.warn("Tried to upload VCF of {} while it was already uploading", patientID);
return;
} else if (this.currentRemovals.get(patientID) != null) {
this.logger.warn("Tried to upload VCF of {} while it was being removed", patientID);
return;
}
boolean isPublic = DefaultVCFUploadManager.resolvePatientPermission(patient);
Future varStoreFuture = null;
try {
varStoreFuture = this.varStore.addIndividual(patientID, isPublic, vcfFile.toPath());
VCFUploadJob newUploadJob = new VCFUploadJob(patient, varStoreFuture, contextProvider,
this.observationManager);
ExecutionContextRunnable wrappedJob = new ExecutionContextRunnable(newUploadJob, componentManager);
this.currentUploads.add(patientID, this.executor.submit(wrappedJob));
} catch (VariantStoreException e) {
this.logger.warn("Variant store exception thrown when trying to upload a vcf for: {}", patientID);
}
}
/**
* {@inheritDoc}
*
* @see org.phenotips.variantStoreIntegration.VCFUploadManager#cancelUpload(org.phenotips.data.Patient)
*/
@Override
public void cancelUpload(Patient patient)
{
String id = patient.getId();
if (this.currentUploads.get(id) != null) {
// TODO: Canceling a Future doesn't actually cancel the running job, it effectively just sets a flag on it.
// The VariantStore should probably implement a way to check if a task is canceled, and undo it.
this.currentUploads.get(id).cancel(true);
this.currentUploads.remove(id);
} else {
this.logger.warn("Tried to cancel the VCF upload of {} but failed because it was not uploading!",
patient.toString());
}
}
/**
* {@inheritDoc}
*
* @see org.phenotips.variantStoreIntegration.VCFUploadManager#removeVCF(org.phenotips.data.Patient)
*/
@Override
public void removeVCF(String patientID) throws Exception
{
Patient patient = this.pr.getPatientById(patientID);
if (patient == null) {
this.logger.warn("No patient found with the id : {}", patientID);
throw new Exception("Could not find the patient with ID : " + patientID);
}
if (this.currentUploads.get(patientID) != null) {
this.logger.warn("Tried to remove the VCF of {} while it was uploading", patientID);
return;
} else if (this.currentRemovals.get(patientID) != null) {
this.logger.warn("Tried to remove the VCF of {} while it was already removing", patientID);
return;
}
Future varStoreFuture = null;
try {
varStoreFuture = this.varStore.removeIndividual(patientID);
VCFRemovalJob newRemovalJob = new VCFRemovalJob(patient, varStoreFuture, contextProvider,
this.observationManager);
ExecutionContextRunnable wrappedJob = new ExecutionContextRunnable(newRemovalJob, componentManager);
this.currentRemovals.add(patientID, this.executor.submit(wrappedJob));
} catch (VariantStoreException e) {
this.logger.warn("Variant store exception thrown when trying to remove a vcf for: {}", patientID);
}
}
/**
* Get a list of patients in the variant store.
*
* @return a list of patients
*/
@Override
public List<String> getUploadedPatients() {
return this.varStore.getAllIndividuals();
}
private static boolean resolvePatientPermission(Patient patient)
{
Visibility patientVisibility = permissions.getPatientAccess(patient).getVisibility();
return (patientVisibility.compareTo(hiddenVisibility) > 0);
}
}