package fi.utu.ville.exercises.model; import java.io.Serializable; import java.util.UUID; import fi.utu.ville.standardutils.AbstractFile; import fi.utu.ville.standardutils.BinaryStringConversionHelper; import fi.utu.ville.standardutils.TempFilesManager; /** * <p> * An implementor of {@link PersistenceHandler} can write a matching {@link ExerciseData} or {@link SubmissionInfo}-object to a byte-array in predetermined * (preferably human readable) format, and can also read an {@link ExerciseData} or {@link SubmissionInfo}-object from a (correctly formed) byte-array. * </p> * <p> * Implementations must meet the constraint * * <pre> * "E or S".equalsSemantically(persistenceHandler.read(parsistenceHandler.save("E or S")) * </pre> * * </p> * <p> * Even though JSON or XML is preferred format the only constraint actually required for correct functionality in ViLLE-exercise-system is that the implementor * can read data that it has written. Experience shows however that human-readable and Java-agnostic techniques such as JSON or XML are more flexible to * refactor and keep backwards compatible, or even export to other programs. * </p> * <p> * In most situations GsonPersistenceHandler (See package edu.vserver.exercises.helpers for more info) can be used as an {@link PersistenceHandler} if * {@link ExerciseData}- and {@link SubmissionInfo}-implementors meet requirements for "gson"-specific Java object to JSON-conversion. An converter for * {@link AbstractFile}s is provided meaning that the object's can contain {@link AbstractFile}-fields. * </p> * <p> * For embedding binary data (possibly files kept in {@link TempFilesManager} -manager temporary folder) to generated XML or generating files from embedded * binary data, you can use {@link BinaryStringConversionHelper}. In {@link ExerciseData}-files this is acceptable only for small files as larger files can be * handled much more efficiently and flexibly using {@link ByRefSaver} and {@link ByRefLoader}. * </p> * * @author Riku Haavisto, Johannes Holvitie * */ public interface PersistenceHandler<E extends ExerciseData, S extends SubmissionInfo> extends Serializable { /** * Saves the exercise type specific data object using the correct {@link ExerciseData} class. Returns the persistent form as a byte-array. * * @param etd * The {@link ExerciseData}-implementor representation to be saved * @param tempManager * temp-files-manager currently in use * @param matHandler * {@link ByRefSaver} to use for saving files * @return byte[] to which the etd is serialized in the form determined by this class * @throws ExerciseException * if save fails */ byte[] saveExerData(E etd, TempFilesManager tempManager, ByRefSaver matHandler) throws ExerciseException; /** * Loads an object of the exercise type specific {@link ExerciseData} -class from a byte-array. * * @param dataPres * The byte-array from which the data is tried to be parsed * @param tempManager * temp-files-manager currently in use * @param matHandler * {@link ByRefLoader} by which saved files can be loaded * @return An object of the exercise type specific {@link ExerciseData} -class * @throws ExerciseException * if load fails */ E loadExerData(byte[] dataPres, TempFilesManager tempManager, ByRefLoader matHandler) throws ExerciseException; /** * Saves the exercise type specific data object using the correct {@link SubmissionInfo} class. Returns a byte array of saved data. * * @param subm * The {@link SubmissionInfo}-implementor to be saved * @param tempManager * temp-files-manager currently in use * @return byte[] to which the subm is serialized in the form determined by this class * @throws ExerciseException * if save fails */ byte[] saveSubmission(S subm, TempFilesManager tempManager) throws ExerciseException; /** * <p> * Loads an object of the exercise type specific {@link SubmissionInfo} -class from a stream. * </p> * <p> * If the {@link SubmissionInfo}-object is loaded for {@link SubmissionStatisticsGiver} (forStatGiver is true) it should be taken into account that multiple * {@link SubmissionInfo}-objects are loaded using same temp-folder. This is a no-issue in most cases as usually data-files are either stored in * {@link ExerciseData} which is loaded only once also for {@link SubmissionStatisticsGiver} or they do not contain any data that could really be analyzed * in a generic manner (meaning that data-files can just be left un-loaded). Potential method (if needed) to implement adding several files to same * temp-folder would be to rename all files with random strings ( eg. {@link UUID #randomUUID()} .toString()) (and check that there is no conflicts), and to * store the new file-names to returned {@link SubmissionInfo}-objects. * </p> * * @param dataPres * The byte-array from which the data is tried to be parsed * @param forStatGiver * this is true if the {@link SubmissionInfo} is loaded for {@link SubmissionStatisticsGiver} meaning that it is possible for many * {@link SubmissionInfo}-objects to be loaded sharing the same temp-folder * @param tempManager * temp-files-manager currently in use * @return An object of the exercise type specific {@link SubmissionInfo} -class * @throws ExerciseException * if load fails */ S loadSubmission(byte[] dataPres, boolean forStatGiver, TempFilesManager tempManager) throws ExerciseException; /** * <p> * Implementors of this class handle saving of resource files ( {@link AbstractFile} in an efficient manner. * </p> * <p> * {@link AbstractFile}s saved through an implementor can be fetched later through {@link ByRefLoader} using the returned id-string. * </p> * <p> * The reference-ids are only meaningful in certain context, eg. references saved by {@link ByRefSaver} in * {@link PersistenceHandler #saveExerData(ExerciseData, TempFilesManager, ByRefSaver) saveExerData()} can be used in fetching files from * {@link ByRefLoader} in {@link PersistenceHandler #loadExerData(byte[], TempFilesManager, ByRefLoader) loadExerData()} used later for loading the same * exercise-instance. * </p> * <p> * No assumptions should be made on how the resource-files are actually stored as this might be changed for performance (etc.) reasons. All functionality * should rely only on the interface represented by {@link AbstractFile}. * </p> * * @see ByRefLoader * @see PersistenceHandler * * @author Riku Haavisto * */ public interface ByRefSaver extends Serializable { /** * Saves the given material-resource ({@link AbstractFile} persistently and returns an reference-id by which it can later be loaded by matching * {@link ByRefLoader} (in 'load'-context matching this 'save'-context). * * @param toSave * {@link AbstractFile} to save * @return {@link String} representing an id by which the resource can later be reloaded * @throws ExerciseException * if some error prevents saving the file */ String saveByReference(AbstractFile toSave) throws ExerciseException; } /** * <p> * Implementors of this class handle efficient loading of resource-files saved through {@link ByRefSaver}. * </p> * <p> * Reference-ids saved after saving {@link AbstractFile}s using {@link ByRefSaver} can be used to load the resource as {@link AbstractFile}s. * </p> * <p> * For further details on for example context in which ref-ids are meaningful see documentation of {@link ByRefSaver}. * </p> * * @see ByRefSaver * @see PersistenceHandler * * @author Riku Haavisto * */ public interface ByRefLoader extends Serializable { /** * Loads and returns an {@link AbstractFile} matching given refId if such material-resource is saved in the matching context (ie. returned after saving * by a {@link ByRefSaver} in symmetrical 'save'-method to current 'load'-method). * * @param refId * id of the resource to load * @return {@link AbstractFile} matching the id * @throws ExerciseException * if resource for the given id does not exists or cannot be loaded * */ AbstractFile loadByReference(String refId) throws ExerciseException; } }