package org.nextprot.api.web.service.impl;
import com.google.common.base.Preconditions;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.nextprot.api.commons.exception.NextProtException;
import org.nextprot.api.commons.service.MasterIdentifierService;
import org.nextprot.api.core.service.EntryBuilderService;
import org.nextprot.api.core.service.ReleaseInfoService;
import org.nextprot.api.core.service.export.format.FileFormat;
import org.nextprot.api.web.NXVelocityContext;
import org.nextprot.api.web.service.ExportService;
import org.nextprot.api.web.service.impl.writer.EntryStreamWriter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.view.velocity.VelocityConfig;
import javax.annotation.PostConstruct;
import java.io.*;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@Service
public class ExportServiceImpl implements ExportService {
private static final Log LOGGER = LogFactory.getLog(ExportServiceImpl.class);
private static final String REPOSITORY_PATH = "repository";
private static final String[] CHROMOSOMES = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "X", "Y", "MT", "unknown" };
@Autowired
private EntryBuilderService entryBuilderService;
@Autowired
private MasterIdentifierService masterIdentifierService;
@Autowired
private VelocityConfig velocityConfig;
@Autowired
private ReleaseInfoService releaseInfoService;
private int numberOfWorkers = 8;
private ExecutorService executor = null;
@Override
public List<Future<File>> exportAllEntries(FileFormat format) {
List<String> uniqueNames = new ArrayList<>();
for (String chrm : CHROMOSOMES) {
uniqueNames.addAll(this.masterIdentifierService.findUniqueNamesOfChromosome(chrm));
}
return exportEntries(uniqueNames, format);
}
@Override
public List<Future<File>> exportEntriesOfChromosome(String chromossome, FileFormat format) {
List<String> uniqueNames = this.masterIdentifierService.findUniqueNamesOfChromosome(chromossome);
return exportEntries(uniqueNames, format);
}
@Override
public List<Future<File>> exportEntries(Collection<String> uniqueNames, FileFormat format) {
List<Future<File>> futures = new ArrayList<>();
futures.add(exportSubPart(SubPart.HEADER, format));
for (String uniqueName : uniqueNames) {
futures.add(exportEntry(uniqueName, format));
}
futures.add(exportSubPart(SubPart.FOOTER, format));
return futures;
}
@PostConstruct
public void init() {
executor = Executors.newFixedThreadPool(numberOfWorkers);
}
private Future<File> exportSubPart(SubPart part, FileFormat format) {
return executor.submit(new ExportSubPartTask(velocityConfig.getVelocityEngine(), part, format));
}
@Override
public Future<File> exportEntry(String uniqueName, FileFormat format) {
return executor.submit(new ExportEntryTask(this.entryBuilderService, velocityConfig.getVelocityEngine(), uniqueName, format));
}
private static class ExportEntryTask implements Callable<File> {
private String format;
private String filename;
private String entryName;
private EntryBuilderService entryBuilderService;
private final Template template;
ExportEntryTask(EntryBuilderService entryBuilderService, VelocityEngine ve, String entryName, FileFormat format) {
Preconditions.checkNotNull(format, "A format should be specified (xml or ttl)");
this.entryBuilderService = entryBuilderService;
this.filename = REPOSITORY_PATH + "/" + format.name() + "/" + entryName + "." + format.getExtension();
//noinspection ResultOfMethodCallIgnored
new File(filename).getParentFile().mkdirs();
this.entryName = entryName;
this.format = format.getExtension();
if (this.format.equals(FileFormat.TURTLE.getExtension())) {
template = ve.getTemplate("turtle/entry." + format + ".vm");
} else {
template = ve.getTemplate("entry." + format + ".vm");
}
}
@Override
public File call() throws Exception {
File f = new File(filename);
if (f.exists()) {
return f;
}
LOGGER.info("Building " + filename);
VelocityContext context = new NXVelocityContext(entryBuilderService.buildWithEverything(entryName));
try (PrintWriter pw = new PrintWriter(new FileWriter(filename, true))) {
template.merge(context, pw);
} catch (Exception e) {
LOGGER.error("Failed to generate " + entryName + " because of " + e.getMessage());
throw e;
}
return f;
}
}
private enum SubPart {
HEADER, FOOTER
}
private static class ExportSubPartTask implements Callable<File> {
private FileFormat npFormat;
private String format;
private VelocityEngine ve;
private String filename;
private SubPart part;
private ExportSubPartTask(VelocityEngine ve, SubPart part, FileFormat format) {
this.npFormat = format;
this.ve = ve;
this.part = part;
this.format = format.getExtension();
this.filename = REPOSITORY_PATH + "/" + format.name() + "/" + part.name() + "." + format.getExtension();
//noinspection ResultOfMethodCallIgnored
new File(filename).getParentFile().mkdirs();
}
private void checkFormatConstraints() {
if (!(npFormat.equals(FileFormat.TURTLE) || npFormat.equals(FileFormat.XML))) {
throw new NextProtException("A format should be specified (xml or ttl)");
}
}
@Override
public File call() throws Exception {
File f = new File(filename);
if (f.exists()) {
return f;
}
checkFormatConstraints();
Template template = null;
VelocityContext context;
if (part.equals(SubPart.HEADER)) {
if (format.equals(FileFormat.TURTLE.getExtension())) {
template = ve.getTemplate("turtle/prefix.ttl.vm");
} else {
template = ve.getTemplate("exportStart.xml.vm");
}
} else if (part.equals(SubPart.FOOTER)) {
if (format.equals(FileFormat.XML.getExtension())) {
template = ve.getTemplate("exportEnd.xml.vm");
}
}
if (template == null) {
template = ve.getTemplate("blank.vm");
}
context = new VelocityContext();
try (FileWriter fw = new FileWriter(filename, true); PrintWriter out = new PrintWriter(new BufferedWriter(fw))) {
template.merge(context, out);
} catch (Exception e) {
LOGGER.error("Failed to generate header because of " + e.getMessage());
throw e;
}
return f;
}
}
@Override
public void clearRepository() {
LOGGER.info("Cleaning export service repository at path: " + REPOSITORY_PATH);
File repo = new File(REPOSITORY_PATH);
if (repo.exists()) {
File[] files = repo.listFiles();
if (files != null) {
for (File file : files) {
//noinspection ResultOfMethodCallIgnored
file.delete();
}
}
}
}
@Value("${export.workers.count}")
public void setNumberOfWorkers(int numberOfWorkers) {
this.numberOfWorkers = numberOfWorkers;
}
@Override
public void streamResults(EntryStreamWriter writer, String viewName, List<String> accessions) throws IOException {
Map<String, Object> map = new HashMap<>();
map.put(ExportService.ENTRIES_COUNT_PARAM, accessions.size());
map.put("release", releaseInfoService.findReleaseInfo());
writer.write(accessions, map);
}
}