package filehandling.generic;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import org.apache.commons.io.FileUtils;
import org.molgenis.core.MolgenisFile;
import org.molgenis.framework.db.Database;
import org.molgenis.util.ValueLabel;
import decorators.MolgenisFileHandler;
import decorators.NameConvention;
public class PerformUpload
{
/**
* Generic file upload. This function assumes the MolgenisFile is already in
* the database, and only a file source corresponding to this MolgenisFile
* is added.
*
* @param db
* @param mf
* @param content
* @throws Exception
* @throws XGAPStorageException
*/
public static void doUpload(Database db, MolgenisFile mf, File content, boolean skipWhenDestExists)
throws Exception
{
File storageForFileType = new MolgenisFileHandler(db).getStorageDirFor(mf.get__Type(), db);
// copy the file in the right place
String storageFileName = NameConvention.escapeFileName(mf.getName());
File dest = new File(storageForFileType.getAbsolutePath() + File.separator + storageFileName + "."
+ mf.getExtension());
copyHelper(content, dest, skipWhenDestExists);
}
/**
* Generic file upload. This function will create a new MolgenisFile object
* in the database, as well as put the referenced file source for this
* MolgenisFile in the correct place. The 'type' must correspond to the name
* of a subclass of MolgenisFile. Fields and types are dynamically checked
* and casted.
*
* @param db
* The database to use.
* @param name
* Full name of the original file (eg. 'mypicture.png')
* @param type
* Type of the file. Must correspond to a MolgenisFile subclass.
* (eg. 'Image' or 'BinaryDataMatrix') Used as part of the
* storage location. (eg. {storagedir}/{deployname}/image)
* @param content
* Any existing file of which only the content is submitted to
* the upload.
* @param extraFields
* A hashmap containing values of the additional fields that are
* used by this subclass of MolgenisFile. For example,
* 'investigation_name' for Image.
* @throws Exception
*/
public static void doUpload(Database db, boolean useTx, String name, String type, File content,
HashMap<String, String> extraFields, boolean skipWhenDestExists) throws Exception
{
// file checks
if (content == null)
{
throw new Exception("File holding content is a nullpointer!");
}
if (!content.exists())
{
throw new Exception("File holding content does not exist: " + content.getAbsolutePath());
}
// see if we need extra fields for this type of MolgenisFile
ArrayList<String> extraNeededFields = getNeededExtraFields(type, db);
// if so, check if they are present in the input
if (extraNeededFields.size() > 0)
{
for (String neededField : extraNeededFields)
{
if (!extraFields.keySet().contains(neededField))
{
throw new Exception("Missing needed field '" + neededField + "' for MolgenisFile type '" + type
+ "'");
}
}
}
// perform checks on filename and extension
fileNameChecks(name);
// filename and extension supposed to be ok for splitting
String[] split = name.split("\\.");
// create new MolgenisFile --> which is actually of the subclass type!
MolgenisFile mfAdd = (MolgenisFile) db.getClassForName(type).newInstance();
// set name and extension
mfAdd.setName(split[0]);
mfAdd.setExtension(split[1]);
// copy extra fields for this type of MolgenisFile
for (String extraField : extraFields.keySet())
{
mfAdd.set(extraField, extraFields.get(extraField));
}
// start db transaction if there is none yet, and useTx is selected,
// otherwise assume the user started a transaction elsewhere and wants
// to manage this him/herself!
boolean txStartedHere = false;
if (useTx && !db.inTx())
{
db.beginTx();
txStartedHere = true;
}
try
{
// try adding to db - if add succeeds, try sorting out the file
db.add(mfAdd);
// get storage dir for this MolgenisFile type
File storageForFileType = new MolgenisFileHandler(db).getStorageDirFor(type, db);
// copy the file in the right place
String storageFileName = NameConvention.escapeFileName(mfAdd.getName());
File dest = new File(storageForFileType.getAbsolutePath() + File.separator + storageFileName + "."
+ mfAdd.getExtension());
copyHelper(content, dest, skipWhenDestExists);
// commit if file is in the right place, and if the transaction is
// started in this function (see comment above for beginTx)
if (txStartedHere)
{
db.commitTx();
}
}
catch (Exception e)
{
// if something fails, rollback and throw error
if (txStartedHere)
{
db.rollbackTx();
}
throw e;
}
}
public static void fileNameChecks(String name) throws Exception
{
// see if trimmed name is empty, and continue to trim if not
if (name.trim().length() == 0)
{
throw new Exception("No filename specified");
}
name = name.trim();
// we know: filename (already trimmed) has length > 0
// now check of starting or ending '.'
if (name.startsWith(".") || name.endsWith("."))
{
throw new Exception("Please provide proper filename (not starting or ending with '.')");
}
// file name must contain 1 '.'
if (name.contains("."))
{
int countPeriods = 0;
for (char c : name.toCharArray())
{
if (c == '.')
{
countPeriods++;
}
if (countPeriods > 1)
{
throw new Exception("Please provide proper filename (not more than one '.') instead of '" + name
+ "'");
}
}
}
else
{
throw new Exception("No '.' in filename '" + name + "' found - please provide an extension (ie. harry.png)");
}
// split and do some extra checks
String[] split = name.split("\\.");
if ((split.length != 2) || split[0].length() == 0 || split[1].length() == 0)
{
throw new Exception("Error when splitting file name '" + name
+ "' using '.'. Filename doesn't consist of two parts or a part was an empty string.");
}
}
/**
* Reusable function that will return a list of fieldnames that are needed
* for this subclass of 'MolgenisFile'.
*
* @param type
* @param db
* @return
* @throws Exception
*/
public static ArrayList<String> getNeededExtraFields(String type, Database db) throws Exception
{
ArrayList<String> result = new ArrayList<String>();
// create regular MolgenisFile
MolgenisFile mfSuperClass = new MolgenisFile();
// add all regular fields to a list
ArrayList<String> options = new ArrayList<String>();
for (ValueLabel vl : mfSuperClass.get__TypeOptions())
{
options.add(vl.getValue().toString());
}
// check if there is a subclass for this type
if (!options.contains(type))
{
throw new Exception("Type '" + type
+ "' is not a subclass of MolgenisFile. Maybe you should use uppercase?");
}
// try to instantiate the subclass
MolgenisFile mfSubClass = (MolgenisFile) db.getClassForName(type).newInstance();
// DISCUSSION:
// maybe use..
// JDBCMetaDatabase meta = new JDBCMetaDatabase();
// org.molgenis.model.elements.Entity entity = meta.getEntity("Marker");
// for(org.molgenis.model.elements.Field f : entity.getFields()){
// f.isNillable();
// }
// iterate through the fields of a subclassed type, and find any
// additional fields however don't add them if they have a '_name'
for (String subClassField : mfSubClass.getFields())
{
// if the field is not in MolgenisFile
if (!mfSuperClass.getFields().contains(subClassField))
{
// field has no '_name' (a convenient unique xref pointer)
if (!subClassField.endsWith("_name"))
{
// check if there is a '_name' ending field, only add if
// there is no such field
if (!(mfSubClass.getFields().contains(subClassField + "_name")))
{
result.add(subClassField);
}
}
else
{
// field has '_name', so use it in any case
result.add(subClassField);
}
}
}
return result;
}
public static void copyHelper(File content, File dest, boolean skipWhenDestExists) throws IOException
{
if (skipWhenDestExists)
{
if (!dest.exists())
{
FileUtils.copyFile(content, dest);
}
}
else
{
if (dest.exists())
{
throw new IOException("Destination file " + dest.getAbsolutePath() + " already exists");
}
else
{
FileUtils.copyFile(content, dest);
}
}
}
}