Na ukázku konfigurace a naprogramování validačního procesu zde uvádím příklad validace průběžných příkladů odevzdávaných studenty předmětu KIV/PPA1 (Počítače a programování - základní předmět, výuka Javy) na Západočeské univerzitě v Plzni.
Předpokládáme, že validační server běží na stroji vs.kiv.zcu.cz.
Je k dispozici aktualizovaný dokument "návod pro práci s validátorem pro učitele předmětu". Najdete tam mimo jiné rozebraný problém s analýzou výstupů programů.
Zdroj této kapitoly: WWW stránky předmětu KIV/PPA1.
Jedná se o relativně krátké programy, které se tematicky váží k látce probírané na příslušném cvičení. Jsou součástí většiny cvičení. Odevzdávají se pouze elektronicky pomocí Portálu a jsou okamžitě vyhodnoceny pomocí validátoru.
Smysl domácích úloh je postupně vést studenty k řešení stále složitějších programů. Proto je důležité domácí úkoly vypracovávat průběžně.
Studentský program musí mít totožný výstup, jako referenční program připravený vyučujícím. Shodnost výstupů studentova a referenčního programu kontroluje tzv. "validační server", který je automaticky aktivován ihned po elektronickém odevzdání studentova programu.
Validační server ohlásí výsledek validace a v případě, že je neúspěšný, poskytne i chybové hlášení. Student se tak může pokoušet odevzdávat svůj program tak dlouho, dokud nebude úspěšný. Neúspěšné pokusy se nepočítají.
Student má k dispozici referenční program (.class soubor) a také ukázky typických výstupů referenčního programu.
Výhody:
Nevýhody:
Dodržování pravidel/konvencí je ale v předmětu založeném na programování (narozdíl od umělecky založených předmětů) žádoucí akceptovat.
Existují velmi podrobné návody, jak na to a několik postupně navazujících podpor. Celý postup odevzdávání si studenti mají možnost nezávazně třikrát nacvičit. Navíc je vždy možné se při cvičeních zeptat cvičícího.
Existují prostředky, jak toto chování odhalit.
Studenti mají dále k dispozici tuto stránku s informacemi. Popisuje přesně způsob odevzdání, pojmenování souborů atd. Doporučuji přečíst alespoň zběžně, než budete pokračovat ve čtení této případové studie.
Ve validačním serveru je pro účely tohoto předmětu doména "ppa1", nahrána samozřejmě v adresáři "data/domains/ppa1". Konfigurační soubor domény vypadá následovně:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties version="1.0"> <comment>Validacni domena pro predmet PPA1</comment> <!-- ucuje, zda je zapnut 'debug' rezim, tj. zda mj. prochazi kompletni stacktrace do vystupnich html --> <entry key="core.debug">0</entry> <!-- zda po sobe uklizet docasne soubory - pro ladeni. 1/0 --> <entry key="core.do_cleanup">0</entry> <!-- pokud je core.concurrency_disabled = 1, pak je zakazan jakykoliv paralelismus, tj. ulohy validace se spousteji za sebou --> <entry key="core.concurrency_disabled">0</entry> <!-- maximalni velikost odevzdaneho souboru v KB --> <entry key="max_file_size">50</entry> <!-- seznam pripon souboru, ktere se akceptuji. Pokud neni uvedeno nebo obsahuje znak '*', akceptuji se vsechny soubory --> <entry key="accept_extensions">java</entry> <!-- maxilani doba celeho procesu validace v teto domene sekundach. Default hodnota je 30 vterin, neni-li uvedena --> <entry key="max_validation_time">300</entry> <!-- nazev souboru s lokalizacnimi hlaskami pro tuhle domenu. Cesta je relativne vuci adresari domeny! --> <entry key="resource_bundle">nl.ppa1</entry> <!-- nazev souboru s java policy nastavenim. Vyuzije se v pripade, kdy se spousti Java tridy. Pote je tenhle soubor predan noev spustenemu JVM jako parametr a z nej se nactou bezpecnostni opatreni. Cesta je relativne k adreasri domeny.--> <entry key="java_policy_file">run.policy</entry> <!-- Jake vsechny vystupy validace generovat --> <entry key="custom.output.classes">cz.zcu.validationserver.output.HTMLResultOutput; cz.zcu.validationserver.output.XMLResultOutput; cz.zcu.validationserver.output.PPA1MySQLOutput</entry> <!-- jake generovat typy vystupu: moznosti: 'file', 'string' ci oboji oddelene carkou --> <entry key="html_output_type">file</entry> <!-- zde musi byt cesta k html adresari, kam se ukladaji vysledne html soubory. Tato cesta se nebere relativne vuci data adresari! --> <entry key="html_output_dir">data/html</entry> <!-- zacatek URL k http serveru, pres ktery budou vysledky pristupne --> <entry key="html_output_url">http://localhost/vs/</entry> <!-- url na css styl pro exportovane html soubory --> <entry key="html_css_url">/validator/vs.css</entry> <!-- zde je cesta k souboru ve formatu XML s jednoduchym "logem" vysledku. Je to soubor, ktery potrebuje Pavel Herout pro svoje statistiky --> <entry key="xml_result_file">data/results.xml</entry> <!-- Konfiguracni udaje pro vystup do MySQL pro PPA1 --> <entry key="output.mysql.server_name">vs.kiv.zcu.cz</entry> <entry key="output.mysql.database">validator</entry> <entry key="output.mysql.user_name">validator</entry> <entry key="output.mysql.password">HESLO</entry> <!-- kde jsou ulozeny vzorove soubory pro PPA1 --> <entry key="ppa1_vzory_dir">data/domains/ppa1/vzory</entry> </properties>
Pro podrobný popis jednotlivých možností viz konfigurace domény a konfigurace serveru.
Validační server tedy nyní ví, že má generovat HTML soubory s výsledky validace a ty ukládat do adresáře /var/www/validator/ppa1, ví kde je uložen css styl pro tyto soubory, ví, přes jakou URL budou soubory dostupné (http://vs.kiv.zcu.cz/validator/ppa1/). Akceptuje pouze soubory s příponou ".java" a maximální možná doba trvání validace je omezena na 15 vteřin.
V adresáři "data/domains/ppa1/nl" je uložen soubor "ppa1.properties". Jedná se o klasický soubor pro ResourceBundle, tedy soubor s lokalizačními hláškami. Bylo-li by potřeba lokalizovat například do angličtiny, stačí dodat soubor "ppa1_en.properties" atd.
ppa1.prekrocil_compile=Vámi zaslaný soubor se překládal déle, než je povoleno. Buď nastala \ nějaká zvláštní chyba překladače nebo je validační server zaneprázdněný. Zkuste, prosím, \ poslat po chvíli znovu. ppa1.prekrocil_spousteni=Vámi zaslaný soubor běžel mnohem déle, než by měl. Zjistěte, zda \ se v něm neskrývá například nějaký nekonečný cyklus. ppa1.nelze_prelozit_vzor=Interní chyba serveru, respektive zadávajících - nelze přeložit\ vzorový příklad ppa1.neni_podtrzitko=Špatný název odevzdaného souboru - neobsahuje žádné podtržítko ppa1.chybny_autor=Autor uvedený v názvu souboru (za posledním podtržítkem) nesouhlasí \ s uživatelem přihlášeným do portálu ppa1.nejmenovat_vzor=Soubor se nesmí jmenovat 'VZOR'! ppa1.neexistuje_vzor=Neexistuje vzorová třída, podle které by bylo možné program \ zkontrolovat. Zkontrolujte si, prosím, název Vámi odevzdané třídy, zda vyhovuje \ domluveným pravidlům (dejte pozor například i na velikost písmen v názvu souboru - hraje to roli)
Dále je uveden odkaz na soubor "run.policy", který bude využit při spouštění Javovských programů. Jediné, co budou muset studenti ve svých pracích používat řekněme trochu nebezpečného, je přístup k souborům. Proto jim přístup omezíme pouze na adresář, odkud budou soubory číst (a pouze číst). Další informace viz kapitola o zabezpečení:
// Policy soubor pro spousteni vzorovych prikladu // umoznuje cteni/zapis z/do adresare domeny a vsech podadresaru // co muze vzorova trida grant codeBase "file:${validator.classbasedir}/-" { // cteni z domain adresare - budou tam pripadne nejake vstupni soubory permission java.io.FilePermission "${validator.domaindir}/-", "read"; // dalsi "default" opravneni permission java.util.PropertyPermission "java.version", "read"; permission java.util.PropertyPermission "java.vendor", "read"; permission java.util.PropertyPermission "java.vendor.url", "read"; permission java.util.PropertyPermission "java.class.version", "read"; permission java.util.PropertyPermission "os.name", "read"; permission java.util.PropertyPermission "os.version", "read"; permission java.util.PropertyPermission "os.arch", "read"; permission java.util.PropertyPermission "file.separator", "read"; permission java.util.PropertyPermission "path.separator", "read"; permission java.util.PropertyPermission "line.separator", "read"; permission java.util.PropertyPermission "java.specification.version", "read"; permission java.util.PropertyPermission "java.specification.vendor", "read"; permission java.util.PropertyPermission "java.specification.name", "read"; permission java.util.PropertyPermission "java.vm.specification.version", "read"; permission java.util.PropertyPermission "java.vm.specification.vendor", "read"; permission java.util.PropertyPermission "java.vm.specification.name", "read"; permission java.util.PropertyPermission "java.vm.version", "read"; permission java.util.PropertyPermission "java.vm.vendor", "read"; permission java.util.PropertyPermission "java.vm.name", "read"; };
Pouze podotýkám, že všechny PropertyPermission jsou vlastně také pro tento případ zbytečné, studenti je číst nepotřebují.
Následuje seznam všech souborů, které obsahují script validačního procesu. Začneme souborem "data/domains/ppa1/process.xml":
<?xml version="1.0" ?> <!DOCTYPE validation-process PUBLIC "-//Validation-Process //" "process.dtd"> <validation-process> <!-- volani podprocesu, ktery zkontroluje, zda se jedna o Java zdrojak --> <call process="/common/checkjavasource.xml"> <param name="file">inputFile</param> </call> <!-- inicializace PPAcek - tedy urceni nazvu souboru, trid atd. --> <call process="init.xml"/> <!-- kompilace vzoroveho programu, pokud uz neni zkompilovan --> <cache> <file> vzorClass </file> <observed-file> vzorFile </observed-file> <put> <!-- prelozim vzorovy suobor --> <call process="/common/compilejava.xml"> <param name="file">vzorFile</param> </call> <if> <condition> ! validationResult.isStillOK() </condition> <then> <error key="ppa1.nelze_prelozit_vzor" code="SERVER_ERROR"/> <quit/> </then> </if> </put> </cache> <time-limited time="5000" exceed-message="ppa1.prekrocil_compile"> <!-- prelozim zaslany soubor --> <call process="/common/compilejava.xml"> <param name="file">inputFile</param> </call> </time-limited> <quit-if-error/> <script> var index = 1; </script> <time-limited time="5000" exceed-message="ppa1.prekrocil_spousteni"> <!-- telo cyklu se opakuje dokud plati 'condition', pripadne dokud se nezavola prikaz <break> --> <while> <!-- dokud existuje soubor .in<index> --> <condition> inFile = new java.io.File(domainDir,'vzory/'+prikladIdent+'.in'+index); inFile.exists() </condition> <body> <!-- nejdriv zjistim vystup vzoroveho prikladu --> <script> vzorOutput = new java.io.File(vzoryDir,'out/'+vzorClassName+'.out'+index); (new java.io.File(workDir,'out').mkdirs()); testOutput = new java.io.File(workDir,'out/'+className+'.out'+index); </script> <cache> <file>vzorOutput</file> <observed-file>vzorFile</observed-file> <observed-file>inFile</observed-file> <put> <!-- spustim vzorovou tridu --> <call process="/common/invokejava.xml"> <param name="mainClass">vzorClassName</param> <param name="classPath">vzoryDir</param> <param name="stdIn">inFile</param> <param name="stdOut">vzorOutput</param> <param name="stdErr">null</param> <param name="args">null</param> </call> </put> </cache> <!-- nyni zjistim vystup testovane tridy --> <call process="/common/invokejava.xml"> <param name="mainClass">className</param> <param name="classPath">workDir</param> <param name="stdIn">inFile</param> <param name="stdOut">testOutput</param> <param name="stdErr">testOutput</param> <param name="args">null</param> </call> <!-- tyto vystupy porovnam --> <call process="/common/comparefiles.xml"> <param name="file1">vzorOutput</param> <param name="file2">testOutput</param> </call> <script> index++; </script> <quit-if-error/> </body> </while> </time-limited> </validation-process>
Následuje soubor "init.xml", který se volaný z předchozího scriptu:
<?xml version="1.0" ?> <!DOCTYPE validation-process PUBLIC "-//Validation-Process //" "process.dtd"> <!-- inicializace PPA1 --> <validation-process> <!-- inicializace --> <script> inputFileName = inputFile.getName(); className = inputFileName; /* pokud to obsahuje tecku, tak vsechno za ni vcetne ni vyhodim */ var tecka = className.indexOf('.'); if (tecka != -1) { className = className.substring(0, tecka); } /* najdu posledni podtrzitko */ var lastDelim = className.lastIndexOf('_'); </script> <if> <condition>lastDelim == -1</condition> <then> <error key="ppa1.neni_podtrzitko" code="INVALID_INPUT"/> <quit/> </then> </if> <script> /* rozdelim nazev souboru na identifikator prikladu a jmeno autora */ prikladIdent = className.substring(0, lastDelim); autorIdent = className.substring(lastDelim + 1, className.length()); /* ulozim identifikator prikladu do resultu */ validationResult.addCustomResult("priklad", prikladIdent); </script> <!-- souhlasi autor v nazvu souboru s autorem co prisel z portalu? --> <if> <condition> (!autor.toUpperCase().equals("CLIENT")) && (! autorIdent.toUpperCase().equals(autor.toUpperCase()) )</condition> <then> <error key="ppa1.chybny_autor" code="INVALID_INPUT"/> <quit/> </then> </if> <!-- autor se urcite nesmi jmenovat VZOR --> <if> <condition>autorIdent.toUpperCase().equals("VZOR")</condition> <then> <error key="ppa1.nejmenovat_vzor" code="INVALID_INPUT"/> <quit/> </then> </if> <!-- inicializace plno promennych --> <script> str = domain.getDomainProperties().getProperty("ppa1_vzory_dir"); vzoryDir = new java.io.File(str); vzorClassName = prikladIdent+'_Vzor'; vzorFile = new java.io.File(vzoryDir,vzorClassName+'.java'); vzorClass = new java.io.File(vzoryDir,vzorClassName+'.class'); var vzorOut = new java.io.File(vzoryDir, "out"); vzorOut.mkdirs(); </script> <!-- Odevzdava priklad, pro ktery existuje vzor? --> <if> <condition>! vzorFile.exists()</condition> <then> <error key="ppa1.neexistuje_vzor" code="CUSTOM_ERROR"/> <quit/> </then> </if> </validation-process>