package fr.ens.biologie.genomique.eoulsan.core.workflow;
import static com.google.common.base.Preconditions.checkNotNull;
import static fr.ens.biologie.genomique.eoulsan.EoulsanLogger.getLogger;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import fr.ens.biologie.genomique.eoulsan.EoulsanException;
import fr.ens.biologie.genomique.eoulsan.EoulsanRuntimeException;
import fr.ens.biologie.genomique.eoulsan.Globals;
import fr.ens.biologie.genomique.eoulsan.data.Data;
import fr.ens.biologie.genomique.eoulsan.data.DataFile;
/**
* This class define a storage for data metadata of all files generated by the
* workflow.
* @author Laurent Jourdren
* @since 2.0
*/
public class DataMetadataStorage {
private static final String METADATA_FILENAME = ".eoulsanmetadata";
private static final String FIELD_SEPARATOR = "\t";
private static DataMetadataStorage singleton;
private final DataFile metadataFile;
private final Map<String, Map<String, String>> metadata = new HashMap<>();
/**
* Set the metadata of a data from the metadata storage.
* @param data the date which metadata must be set
* @return true if the metadata for the data has been found in the metadata
* storage
*/
public boolean loadMetadata(final Data data) {
checkNotNull(data, "data argument cannot be null");
return loadMetadata(data, WorkflowDataUtils.getDataFiles(data));
}
/**
* Set the metadata of a data from the metadata storage.
* @param data the date which metadata must be set
* @param files files to search in the storage to get metadata
* @return true if the metadata for the data has been found in the metadata
* storage
*/
public boolean loadMetadata(final Data data, final List<DataFile> files) {
checkNotNull(data, "data argument cannot be null");
checkNotNull(files, "files argument cannot be null");
final SimpleDataMetadata metadata =
WorkflowDataUtils.getSimpleMetadata(data.getMetadata());
// Do nothing if metadata cannot be set
if (metadata == null) {
return false;
}
boolean result = false;
// For each file of the data
for (DataFile file : files) {
final String filename = file.getName();
final Map<String, String> entries = this.metadata.get(filename);
// Do nothing if any file is in registry
if (entries != null) {
// Set the values
for (Map.Entry<String, String> e : entries.entrySet()) {
metadata.setRaw(e.getKey(), e.getValue());
}
result = true;
}
}
return result;
}
/**
* Save metadata of a Data object.
* @param data the data object
*/
public void saveMetaData(final Data data) {
checkNotNull(data, "data argument cannot be null");
// If data is a list process of data elements by recursion
if (data.isList()) {
for (Data d : data.getListElements()) {
saveMetaData(d);
}
return;
}
final SimpleDataMetadata metadata =
WorkflowDataUtils.getSimpleMetadata(data.getMetadata());
// Do nothing if metadata cannot be set
if (metadata == null) {
return;
}
final List<DataFile> files = WorkflowDataUtils.getDataFiles(data);
// For each file of the data
for (DataFile file : files) {
final String filename = file.getName();
final Map<String, String> newEntries = new HashMap<>();
final StringBuilder sb = new StringBuilder();
sb.append(filename);
for (String key : metadata.keySet()) {
final String value = metadata.getRaw(key);
newEntries.put(key, value);
sb.append(FIELD_SEPARATOR);
sb.append(key);
sb.append(FIELD_SEPARATOR);
sb.append(value);
}
// If metadata for the file has changed
if (!newEntries.equals(this.metadata.get(filename))) {
// Save entries in memory
this.metadata.put(filename, newEntries);
// Save entries in the file
try {
writeMetadataEntry(sb.toString());
} catch (EoulsanException e) {
getLogger().warning(e.getMessage());
}
}
}
}
//
// Storage methods
//
/**
* Load metadata.
* @throws EoulsanException if an error occurs while reading metadata
*/
private void loadMetaDataEntries() throws EoulsanException {
// Test if storage exists
if (!this.metadataFile.exists()) {
return;
}
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(this.metadataFile.open(),
Globals.DEFAULT_CHARSET))) {
String line = null;
while ((line = reader.readLine()) != null) {
final String[] fields = line.split(FIELD_SEPARATOR);
if (fields.length % 2 != 0) {
final String filename = fields[0];
final Map<String, String> entries = new HashMap<>();
this.metadata.put(filename, entries);
for (int i = 1; i < fields.length; i += 2) {
entries.put(fields[i], fields[i + 1]);
}
}
}
} catch (IOException e) {
throw new EoulsanException("Unable to read metadata: " + e.getMessage(),
e);
}
}
private void writeMetadataEntry(final String s) throws EoulsanException {
// Do nothing if the metadata storage is not on local file
if (!this.metadataFile.isLocalFile()) {
return;
}
try (PrintWriter out = new PrintWriter(new OutputStreamWriter(
new FileOutputStream(this.metadataFile.toFile(), true),
Globals.DEFAULT_CHARSET))) {
// Write entry
out.println(s);
} catch (IOException e) {
throw new EoulsanException("Unable to write metadata: " + e.getMessage(),
e);
}
}
//
// Static method
//
/**
* Get the singleton.
* @return the DataMetadataStorage object
*/
public static DataMetadataStorage getInstance() {
if (singleton == null) {
throw new EoulsanRuntimeException(
"No metadata storage has been previously instanced");
}
return singleton;
}
/**
* Get the singleton.
* @param metadataDir directory where store metadata
* @return the DataMetadataStorage object
*/
public static DataMetadataStorage getInstance(final DataFile metadataDir) {
if (singleton == null) {
singleton = new DataMetadataStorage(metadataDir);
}
return singleton;
}
//
// Constructor
//
/**
* Constructor.
* @param metadataDir directory where store metadata
*/
private DataMetadataStorage(final DataFile metadataDir) {
checkNotNull(metadataDir, "metadataDir argument cannot be null");
// Set the metadata storage file
this.metadataFile = new DataFile(metadataDir, METADATA_FILENAME);
// Load metadata
try {
loadMetaDataEntries();
} catch (EoulsanException e) {
getLogger().warning(e.getMessage());
}
}
}