package matrix.general;
import java.io.File;
import java.io.FileNotFoundException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import matrix.DataMatrixInstance;
import matrix.implementations.binary.BinaryDataMatrixInstance;
import matrix.implementations.csv.CSVDataMatrixInstance;
import matrix.implementations.database.DatabaseDataMatrixInstance;
import org.molgenis.core.MolgenisFile;
import org.molgenis.data.Data;
import org.molgenis.data.DecimalDataElement;
import org.molgenis.data.TextDataElement;
import org.molgenis.framework.db.Database;
import org.molgenis.framework.db.DatabaseException;
import org.molgenis.framework.db.Query;
import org.molgenis.framework.db.QueryRule;
import org.molgenis.framework.db.QueryRule.Operator;
import org.molgenis.framework.db.jdbc.JDBCDatabase;
import org.molgenis.framework.db.jpa.JpaDatabase;
import org.molgenis.util.Entity;
import org.molgenis.util.ValueLabel;
import app.DatabaseFactory;
import decorators.MolgenisFileHandler;
import decorators.NameConvention;
/**
* Class to handle the coupling of 'Data' objects and storage of the actual data
* values
*
* @author joerivandervelde
*
*/
public class DataMatrixHandler extends MolgenisFileHandler
{
public DataMatrixHandler(Database db)
{
super(db);
}
/**
* Create a DataMatrix instance from this 'DataMatrix' object, regardless of
* its source.
*
* @param data
* @return
* @throws Exception
*/
public DataMatrixInstance createInstance(Data data, Database db) throws Exception
{
DataMatrixInstance instance = null;
File source = null;
if (data.getStorage().equals("Database"))
{
instance = new DatabaseDataMatrixInstance(db, data);
}
else
{
source = findSourceFile(data, db);
if (source == null)
{
throw new FileNotFoundException("No MolgenisFile found referring to DataMatrix '" + data.getName()
+ "'");
}
if (data.getStorage().equals("Binary"))
{
instance = new BinaryDataMatrixInstance(source);
}
if (data.getStorage().equals("CSV"))
{
instance = new CSVDataMatrixInstance(data, source);
}
}
return instance;
}
/**
* Wrapper/helper function to attempt to remove a datamatrix. This means
* removing both the source and the 'Data' definition. WARNING: Removing
* 'Data' fails if other entities are still referring to this 'Data', but
* the source is already deleted by then. Only use if you are sure this
* 'Data' can be removed entirely. TODO: Somehow improve this behaviour.
* Make sure there are no other XREF's when considering datasource links,
* then remove source, then remove 'Data'.
*
* @param dm
* @throws Exception
* @throws XGAPStorageException
*/
public void deleteDataMatrix(Data dm, Database db) throws Exception
{
try
{
deleteDataMatrixSource(dm, db);
}
catch (FileNotFoundException fnfe)
{
// no source found, continue to delete 'Data'
}
db.remove(dm);
}
/**
* Delete the source for this DataMatrix. Can be 'Database' or any kind of
* MolgenisFile subclass. (ie. 'Binary', 'CSV')
*
* @param dm
* @throws Exception
* @throws XGAPStorageException
*/
public void deleteDataMatrixSource(Data dm, Database db) throws Exception
{
String verifiedSource = findSource(dm, db);
if (verifiedSource.equals("null"))
{
throw new FileNotFoundException("No source to delete, appears to be null.");
}
else
{
if (verifiedSource.equals("Database"))
{
if (dm.getValueType().equals("Decimal"))
{
List<DecimalDataElement> dde = db.find(DecimalDataElement.class, new QueryRule("data_id",
Operator.EQUALS, dm.getId()));
db.remove(dde);
}
else
{
List<TextDataElement> tde = db.find(TextDataElement.class, new QueryRule("data_id",
Operator.EQUALS, dm.getId()));
db.remove(tde);
}
}
else
{
db.remove(findMolgenisFile(dm, db));
}
}
}
/**
* Simple check to find out if this 'DataMatrix' has any kind of data source
* attached. (database, file, etc)
*
* @param data
* @return
* @throws Exception
* @throws XGAPStorageException
*/
public boolean hasSource(Data data, Database db) throws Exception
{
if (data.getStorage().equals("Database"))
{
if (this.isDataMatrixStoredInDatabase(data, db))
{
return true;
}
}
else
{
if (this.findSourceFile(data, db) != null)
{
return true;
}
}
return false;
}
/**
* Enquire if this 'DataMatrix' is stored in a certain way. For example,
* 'Binary'.
*
* @param data
* @param source
* @return
* @throws Exception
* @throws XGAPStorageException
*/
public boolean isDataStoredIn(Data data, String source, Database db) throws Exception
{
ArrayList<String> options = new ArrayList<String>();
for (ValueLabel option : data.getStorageOptions())
{
options.add(option.getValue().toString());
}
if (!options.contains(source))
{
throw new DatabaseException("Invalid source type: " + source);
}
if (source.equals("Database"))
{
return isDataMatrixStoredInDatabase(data, db);
}
else
{
String matrixSource = source + "DataMatrix";
// FIXME: pull out?
List<? extends Entity> test = db.find(db.getClassForName(matrixSource));
for (Entity e : test)
{
// used to be: if
// ((e.get("data_name").toString()).equals(data.getName()))
if ((db instanceof JDBCDatabase && new Integer(e.get("data_id").toString()).intValue() == data.getId()
.intValue())
|| (db instanceof JpaDatabase && ((Data) e.get("data")).getId().intValue() == data.getId()
.intValue()))
{
try
{
this.getFile(e.get("name").toString(), db);
return true;
}
catch (FileNotFoundException fnf)
{
return false;
}
}
}
}
return false;
}
/**
* Helper-like function to find out if a 'DataMatrix' has values stored
* inside the database
*
* @param data
* @return
* @throws DatabaseException
* @throws ParseException
*/
public boolean isDataMatrixStoredInDatabase(Data data, Database db) throws DatabaseException, ParseException
{
Query<DecimalDataElement> dde = db.query(DecimalDataElement.class);
dde.limit(1);
dde.equals("data_id", data.getId());
Query<TextDataElement> tde = db.query(TextDataElement.class);
tde.limit(1);
tde.equals("data_id", data.getId());
boolean hasDataElements = (dde.find().size() > 0 || tde.find().size() > 0) ? true : false;
return hasDataElements;
}
/**
* Find a 'DataMatrix' object that possibly belongs as a field in this
* subtype of MolgenisFile
*
* @param mf
* @return
* @throws DatabaseException
*/
public Data findData(MolgenisFile mf, Database db) throws DatabaseException
{
QueryRule mfName = new QueryRule("name", Operator.EQUALS, mf.getName());
List<? extends Entity> mfToEntity = db.find(db.getClassForName(mf.get__Type()), mfName);
if (mfToEntity.size() == 0)
{
throw new DatabaseException("No entities found for subclass '" + mf.get__Type() + "' and name '"
+ mf.getName() + "'");
}
else if (mfToEntity.size() > 1)
{
throw new DatabaseException("SEVERE: Multiple entities found for subclass '" + mf.get__Type()
+ "' and name '" + mf.getName() + "'");
}
String dataMatrixName = mfToEntity.get(0).get("dataMatrix_name").toString();
QueryRule dataName = new QueryRule("name", Operator.EQUALS, dataMatrixName);
List<Data> dataList = db.find(Data.class, dataName);
if (dataList.size() == 0)
{
throw new DatabaseException("No matrix found for name '" + dataName + "'");
}
else if (dataList.size() > 1)
{
throw new DatabaseException("SEVERE: Multiple matrices found for name '" + dataName + "'");
}
else
{
return dataList.get(0);
}
}
/**
* Find a 'MolgenisFile' object that possibly belongs to DataMatrix.
*
* @param dm
* @return
* @throws DatabaseException
*/
public MolgenisFile findMolgenisFile(Data dm, Database db) throws DatabaseException
{
String matrixSource = dm.getStorage() + "DataMatrix";
List<? extends Entity> test = db.find(db.getClassForName(matrixSource));
for (Entity e : test)
{
if (Integer.valueOf(e.get("data_id").toString()).intValue() == dm.getId().intValue())
{
QueryRule mfId = new QueryRule("id", Operator.EQUALS, e.get(e.getIdField()));
return db.find(MolgenisFile.class, mfId).get(0);
// alternative: by name
// db.find(mfClass, new QueryRule(MolgenisFile.NAME,
// Operator.EQUALS,
// NameConvention.escapeFileName(data.getName())));
}
}
return null;
}
/**
* Find the source file that belongs to this 'DataMatrix'. Returns null if
* not found or not applicable.
*
* @param data
* @return
* @throws Exception
* @throws XGAPStorageException
*/
public File findSourceFile(Data data, Database db) throws Exception
{
List<? extends Entity> mfSubclasses = db.find(db.getClassForName(data.getStorage() + "DataMatrix"));
for (Entity e : mfSubclasses)
{
// used to be: if
// ((e.get("data_name").toString()).equals(data.getName()))
if ((db instanceof JDBCDatabase && new Integer(e.get("data_id").toString()).intValue() == data.getId()
.intValue())
|| (db instanceof JpaDatabase && ((Data) e.get("data_id")).getId().intValue() == data.getId()
.intValue()))
{
return this.getFile(e.get("name").toString(), db);
}
}
return null;
}
/**
* Iterate through all possible sources and return the source option (ie.
* 'Database', 'CSV') if there is a confirmed backend for this option.
*
* @param data
* @return
* @throws Exception
* @throws XGAPStorageException
*/
public String findSource(Data data, Database db) throws Exception
{
ArrayList<String> options = new ArrayList<String>();
for (ValueLabel option : data.getStorageOptions())
{
options.add(option.getValue().toString());
}
for (String option : options)
{
if (isDataStoredIn(data, option, db))
{
return option;
}
}
return "null";
}
/**
* Example usage
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception
{
Database db = DatabaseFactory.create("gcc.properties");
DataMatrixHandler mh = new DataMatrixHandler(db);
Data dm = db.find(Data.class).get(0);
mh.isDataStoredIn(dm, "Binary", db);
}
/**
* Attempt to link this 'Data' object to a backend file that might be there,
* but is not properly attached via MolgenisFile.
*
* @param data
* @param storage
* @param db
* @throws Exception
*/
public boolean attemptStorageRelink(Data data, String storage, Database db) throws Exception
{
if (storage.equals("Database"))
{
return false;
}
boolean relinked = false;
boolean mfPresent = false;
// found out if there already is a MolgenisFile for this 'Data'
if (findMolgenisFile(data, db) != null)
{
mfPresent = true;
}
// find out if there is a file that can be coupled using a new
// MolgenisFile object
boolean filePresent;
String type = data.getStorage() + "DataMatrix";
try
{
this.getFileDirectly(NameConvention.escapeFileName(data.getName()), getExtension(data.getStorage()), type,
db);
filePresent = true;
}
catch (FileNotFoundException fnfe)
{
filePresent = false;
}
// find out if there is a MolgenisFile for the escaped file name
// already, else adding of a Data record
// with a name that is allowed at first still fails due to relinking,
// e.g. an immediate error after adding:
// File name 'metaboliteExpression' already exists in database when
// escaped to filesafe format. ('metaboliteexpression')
boolean escapedMolgenisFileNameExists = false;
if (db.find(db.getClassForName(type),
new QueryRule(MolgenisFile.NAME, Operator.EQUALS, NameConvention.escapeFileName(data.getName())))
.size() > 0)
{
escapedMolgenisFileNameExists = true;
}
// if there is no MolgenisFile, but there is a file present, make the
// link
if (!mfPresent && filePresent && !escapedMolgenisFileNameExists)
{
MolgenisFile mfAdd = (MolgenisFile) db.getClassForName(type).newInstance();
mfAdd.setName(data.getName());
mfAdd.setExtension(getExtension(data.getStorage()));
mfAdd.set("data_" + Data.ID, data.getId().toString());
mfAdd.set("data_" + Data.NAME, data.getName());
db.add(mfAdd);
relinked = true;
}
return relinked;
}
public String getExtension(String storage) throws Exception
{
if (storage.equals("Binary"))
{
return "bin";
}
else if (storage.equals("CSV"))
{
return "txt";
}
else
{
throw new Exception("No extension for '" + storage + "'");
}
}
}