/** * Runs plugin with supplied arguments. * * @li First element in @c $args must be a path to ZIP archive with input data. * @li Plugin execution is stopped on all triggered errors or uncaught exceptions * and plugin error is returned. * @li setUp() and execute() methods are run respectively after input is * unpacked and output folder is created. * @li Plugin creates and later removes two temporary folders. It can also create * one ZIP archive with plugin output (path is returned in plugin results). * * @param array $args simple array with plugin arguments * @return PluginResponse plugin results or error */ public final function run(array $args) { set_error_handler(array('asm\\utils\\Utils', 'turnErrorToException')); $cwd = getcwd(); try { if ($args == null || count($args) < 1) { throw new PluginUseException('Data file argument missing'); } $this->dataFolder = Filesystem::tempDir(); $dataFile = array_shift($args); if (!Compression::unzip($dataFile, $this->dataFolder, $unzipMessage)) { $response = PluginResponse::createError("ZIP extraction failed (" . $unzipMessage . ").\n\nPerhaps you did not submit a ZIP file " . "or that file was corrupted during upload. You may try again. Extraction was attempted to folder " . str_replace("\\", "/", $this->dataFolder)); } else { $this->outputFolder = Filesystem::tempDir(); chdir($this->dataFolder); $this->setUp($args); $this->execute(); chdir($cwd); $outputFile = $this->packOutput(); $outputPath = $outputFile != null ? realpath($outputFile) : null; $response = PluginResponse::create($this->results, $outputPath); } } catch (PluginException $e) { $response = PluginResponse::createError($e->getMessage()); } catch (Exception $e) { $response = PluginResponse::createError('Runtime error: ' . $e->getMessage() . " (file " . $e->getFile() . ", line " . $e->getLine()); } // If an exception occurred during $this->setUp or $this->execute, we must still change the current directory back, // in case more plugins are to be run (in a test case battery) chdir($cwd); restore_error_handler(); if ($this->dataFolder != null) { Filesystem::removeDir($this->dataFolder); } if ($this->outputFolder != null) { Filesystem::removeDir($this->outputFolder); } return $response; }
function run($array) { $zipFile = $array[0]; if (count($array) !== 2) { throw new InvalidArgumentException("This must receive 2 parameters in the array exactly."); } $launcher = new \asm\core\JavaLauncher(); $pluginResults = ""; $response = null; $error = $launcher->launch(Filesystem::combinePaths(CheckerRunner::$xmlCheckRoot, "/files/plugins/XML XQuery/XQueryPlugin.jar"), array($zipFile, $array[1]), $responseString); if (!$error) { if (isset($responseString)) { try { $response = \asm\plugin\PluginResponse::fromXmlString($responseString); } catch (Exception $ex) { $response = \asm\plugin\PluginResponse::createError('Internal error. Plugin did not supply valid response XML and this error occured: ' . $ex->getMessage() . '. Plugin instead supplied this response string: ' . $responseString); } } } else { $response = \asm\plugin\PluginResponse::createError('Plugin cannot be launched (' . $error . ').'); } return $response; }
/** * Launches plugin and updates database with results. * @param string $pluginType one of 'php', 'java', or 'exe' * @param string $pluginFile plugin file path * @param string $inputFile input file path * @param boolean $isTest is this a plugin test or a submission? true if plugin test * @param int $rowId ID of row to be updated * @param array $arguments plugin arguments * @return bool true if no error occurred */ public static function launchPlugin($pluginType, $pluginFile, $inputFile, $isTest, $rowId, $arguments = array()) { try { $response = null; if (!is_file($pluginFile) || !is_file($inputFile)) { $error = "plugin file and/or input file don't exist"; } else { array_unshift($arguments, $inputFile); $cwd = getcwd(); chdir(dirname($pluginFile)); switch ($pluginType) { case 'php': $launcher = new PhpLauncher(); ob_start(); $error = $launcher->launch($pluginFile, $arguments, $response); ob_end_clean(); break; case 'java': $launcher = new JavaLauncher(); $error = $launcher->launch($pluginFile, $arguments, $responseString); break; case 'exe': $launcher = new ExeLauncher(); $error = $launcher->launch($pluginFile, $arguments, $responseString); break; default: $error = "unsupported plugin type '{$pluginType}'"; } chdir($cwd); } if (!$error) { if (isset($responseString)) { try { $response = PluginResponse::fromXmlString($responseString); } catch (Exception $ex) { $response = PluginResponse::createError('Internal error. Plugin did not supply valid response XML and this error occurred: ' . $ex->getMessage() . '. Plugin instead supplied this response string: ' . $responseString); } } } else { $response = PluginResponse::createError('Plugin cannot be launched (' . $error . ').'); } $outputFile = $response->getOutput(); if ($outputFile) { $outputFolder = Config::get('paths', 'output'); $newFile = $outputFolder . date('Y-m-d_H-i-s_') . StringUtils::randomString(10) . '.zip'; if (rename($outputFile, $newFile)) { $outputFile = $newFile; } else { $outputFile = 'tmp-file-rename-failed'; } } /** * @var $pluginTest \PluginTest * @var $submission \Submission * @var $previousSubmissions \Submission[] */ if ($isTest) { $pluginTest = Repositories::findEntity(Repositories::PluginTest, $rowId); $pluginTest->setStatus(\PluginTest::STATUS_COMPLETED); $pluginTest->setSuccess($response->getFulfillment()); $pluginTest->setInfo($response->getDetails()); $pluginTest->setOutput($outputFile); Repositories::persistAndFlush($pluginTest); } else { $submission = Repositories::findEntity(Repositories::Submission, $rowId); // There is a sort of a race condition in here. // It is, in theory, possible, that there will be two submissions with the "latest" status after all is done // This should not happen in practice, though, and even if it does, it will have negligible negative effects. $previousSubmissions = Repositories::makeDqlQuery("SELECT s FROM \\Submission s WHERE s.user = :sameUser AND s.assignment = :sameAssignment AND s.status != 'graded' AND s.status != 'deleted'")->setParameter('sameUser', $submission->getUser()->getId())->setParameter('sameAssignment', $submission->getAssignment()->getId())->getResult(); foreach ($previousSubmissions as $previousSubmission) { $previousSubmission->setStatus(\Submission::STATUS_NORMAL); Repositories::getEntityManager()->persist($previousSubmission); } $submission->setStatus(\Submission::STATUS_LATEST); $submission->setInfo($response->getDetails()); $submission->setSuccess($response->getFulfillment()); $submission->setOutputfile($outputFile); Repositories::getEntityManager()->persist($submission); Repositories::flushAll(); } return !$error; } catch (Exception $exception) { $errorInformation = "Internal error. Plugin launcher or plugin failed with an internal error. Exception information: " . $exception->getMessage() . " in " . $exception->getFile() . " at " . $exception->getLine(); if ($isTest) { $pluginTest = Repositories::findEntity(Repositories::PluginTest, $rowId); $pluginTest->setStatus(\PluginTest::STATUS_COMPLETED); $pluginTest->setSuccess(0); $pluginTest->setInfo($errorInformation); Repositories::persistAndFlush($pluginTest); } else { $submission = Repositories::findEntity(Repositories::Submission, $rowId); $submission->setStatus(\Submission::STATUS_NORMAL); $submission->setInfo($errorInformation); Repositories::persistAndFlush($submission); } return false; } }