package se.chalmers.gdcn.files; import com.google.gson.Gson; import net.tomp2p.peers.Number160; import se.chalmers.gdcn.communicationToUI.CommandWord; import se.chalmers.gdcn.communicationToUI.NetworkInterface; import se.chalmers.gdcn.communicationToUI.OperationFinishedEvent; import se.chalmers.gdcn.taskbuilder.Task; import se.chalmers.gdcn.taskbuilder.communicationToClient.TaskFailureListener; import se.chalmers.gdcn.taskbuilder.communicationToClient.TaskListener; import se.chalmers.gdcn.taskbuilder.fileManagement.PathManager; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.*; import java.util.*; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Created by HalfLeif on 2014-03-04. * * Abstract class for resolving task file dependencies. * * Uses TaskListener to report error information. */ abstract class AbstractFileMaster{ protected final TaskMeta taskMeta; protected final PathManager pathManager; protected final NetworkInterface client; private final TaskFailureListener taskFailureListener; private final CommandWord expectedOperation; private final Lock lock = new ReentrantLock(); private final Condition allDependenciesComplete = lock.newCondition(); private final Map<Number160, FileDep> unresolvedFiles = new HashMap<>(); private volatile boolean operationFailed = false; private volatile boolean stillStartingUp = true; private final PropertyChangeListener operationListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (evt instanceof OperationFinishedEvent) { operationReturned((OperationFinishedEvent) evt); } } }; /** * Creates FileMaster object that reads meta-file for a task. Run {@link AbstractFileMaster#runAndAwait()} for * solving the dependencies. * * * @param taskMeta Dependencies to be solved * @param client Client for downloading files from network (DHT) * @param taskFailureListener Listener to learn about failures such as unresolved dependencies. * @param expectedOperation What kind of operation this object will wait for * @param pathManager PathManager to correct directory * @throws se.chalmers.gdcn.files.TaskMetaDataException if meta-file is not found. Path to search on is derived from projectName and taskName. */ public AbstractFileMaster(TaskMeta taskMeta, NetworkInterface client, TaskFailureListener taskFailureListener, CommandWord expectedOperation, PathManager pathManager) throws TaskMetaDataException { this.taskMeta = taskMeta; this.client = client; this.taskFailureListener = taskFailureListener; this.expectedOperation = expectedOperation; this.pathManager = pathManager; client.addListener(operationListener); for(FileDep fileDep : taskMeta.getDependencies()){ unresolvedFiles.put(fileDep.getDhtKey(), fileDep); } if(taskMeta.getModule() != null){ //is currently null when coming from JobUploader class unresolvedFiles.put(taskMeta.getModule().getDhtKey(), taskMeta.getModule()); } } /** * Parses file for MetaData of Task * * @param file Path to meta data file * @return Representation of meta data content * @throws FileNotFoundException if file isn't found */ protected static TaskMeta readMetaFile(File file) throws FileNotFoundException { Reader reader = null; try { reader = new InputStreamReader(new BufferedInputStream(new FileInputStream(file))); Gson gson = new Gson(); return gson.fromJson(reader, TaskMeta.class); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * Just runs {@link AbstractFileMaster#run()} and {@link AbstractFileMaster#await()} * @return result of {@link AbstractFileMaster#await()} */ public boolean runAndAwait() throws TaskMetaDataException { run(); return await(); } /** * Attempts to resolve the dependencies found in meta-file. */ private void run() throws TaskMetaDataException { if(taskMeta != null){ resolveDependencies(); } else { throw new TaskMetaDataException("Meta data file wasn't found (or parsed correctly)!"); } } /** * Blocks current thread until all dependencies for the specified task are resolved. * * @return true if file has been properly downloaded, false if one of the dependencies couldn't be resolved. */ private boolean await(){ if(operationFailed){ // This code is necessary to avoid deadlock if await() is called after a GET operation has failed // since there is no guarantee for another signal (it might have been the last file to be resolved) System.out.println("Operation failed before enter loop, return FALSE"); client.removeListener(operationListener); return false; } while(stillStartingUp || unresolvedFiles.size()>0){ try { lock.lock(); allDependenciesComplete.await(); lock.unlock(); } catch (InterruptedException e) { System.out.println("Caught interruption: "+e.getMessage()); continue; } if(operationFailed){ client.removeListener(operationListener); return false; } } client.removeListener(operationListener); return true; } /** * Attempt to solve dependencies that was found * @throws TaskMetaDataException if dependent File exist locally but is a directory */ private void resolveDependencies() throws TaskMetaDataException { //gets ConcurrentModificationException if reads directly from unresolvedFiles. Set<FileDep> deps = new HashSet<>(unresolvedFiles.values()); for(FileDep fileDep : deps){ File file = FileManagementUtils.pathTo(pathManager, fileDep); if(file.exists()){ if(file.isDirectory()){ throw new TaskMetaDataException("Files in dependencies should not be directories! File: "+file); } ifFileExist(fileDep); } else { ifFileDoNotExist(fileDep); } } lock.lock(); stillStartingUp = false; allDependenciesComplete.signalAll(); lock.unlock(); } /** * This file dependency was found locally. What to do? * * Called in resolve dependencies * * @param fileDep file */ protected abstract void ifFileExist(FileDep fileDep); /** * This file dependency wasn't found locally. What to do? * * Called in resolve dependencies * * @param fileDep file */ protected abstract void ifFileDoNotExist(FileDep fileDep); /** * Call to set a file dependency as resolved. * @param fileDep file */ protected final void fileDependencyResolved(FileDep fileDep){ lock.lock(); unresolvedFiles.remove(fileDep.getDhtKey()); allDependenciesComplete.signalAll(); lock.unlock(); } /** * Operation successful with respect to this file * @param fileDep file * @param result Result of OperationEvent, may be null */ protected abstract void operationForDependentFileSuccess(FileDep fileDep, Object result); /** * Handles returns of Get operation requested earlier. * @param event OperationFinishedEvent */ private void operationReturned(OperationFinishedEvent event) { if(event.getCommandWord() != expectedOperation){ return; } Number160 key = new Number160(event.getOperation().getKey().toString()); if(!unresolvedFiles.containsKey(key)){ System.out.println("FileDep with key ("+key+") wasn't found in Map for task"); //Might be from other request unrelated with this FileMaster return; } lock.lock(); FileDep fileDep = unresolvedFiles.remove(key); if(event.getOperation().isSuccess()){ operationForDependentFileSuccess(fileDep, event.getOperation().getResult()); } else { operationFailed = true; taskFailureListener.taskFailed(taskMeta.getTaskName(), "Failed to resolve file with name " + fileDep.getFileName()); allDependenciesComplete.signalAll(); } if(unresolvedFiles.size()==0){ allDependenciesComplete.signalAll(); } lock.unlock(); } public String futureResultFilePath(){ return pathManager.getResultFilePath(taskMeta.getTaskName()); } /** * Build new Task specified by the meta-file that was parsed earlier. * @param listener Listener for success on task * @return Task object */ public Task buildTask(TaskListener listener){ return new Task(pathManager.getProjectName(), taskMeta.getTaskName(), FileManagementUtils.moduleName(taskMeta), FileManagementUtils.getResourceFiles(pathManager, taskMeta), listener); } /** * Generates a suitable json-String to put in a file, used for debugging * @param args empty array */ public static void main(String[] args){ FileDep rawIndata = new FileDep("2_2000.raw", "resources", Number160.createHash("Primes_2_2000"), false, 25); List<FileDep> deps = new ArrayList<>(); deps.add(rawIndata); FileDep algorithm = new FileDep("Prime.hs", "code", Number160.createHash("Primes_algorithms"), true, 500); TaskMeta taskMetaTest = new TaskMeta("PrimeTask_01", algorithm, deps); System.out.println( new Gson().toJson(taskMetaTest)); } }