jPDl a BeanShell – jak verifikovat syntax

Jedním ze základních stavebních kamenů našeho identity manageru CzechIdM jsou, kromě standardních Java tříd, JBPM workflow a pravidla napsaná v jazyce jPDL, jejichž součástí jsou BeanShell skripty. Během vývoje se často potýkáme s neexistencí pořádného vývojového prostředí – Eclipse si s BeanShell skripty schovanými v XML neporadí a při vývoji nepomůžou. Ubohý programátor se tak o chybějícím středníku dozví až po deployi na testovací server. Posledních pár dnů jsem proto věnoval vývoji jednoduché aplikace, která by syntaktické chyby v BeanShellu rychle poznala. Po internetu tolik žádaný jPDL plugin do Eclipse to sice není, ale i tak by se vám mohla hodit…

O čem bude vlastně řeč

Pokud se ve všech těch zkratkách v úvodním odstavci malinko ztrácíte, nezoufejte, brzy se dozvíte víc. JBPM je obecná technologie pro zpracování business procesů, takzvaných workflow. Každé workflow se skládá z uzlů a přechodů mezi nimi – není to nic jiného než stavový automat. Jazyk, ve kterém je možné workflow definovat, se jmenuje jPDL. Uzly a přechody se s jeho pomocí popisují v XML souboru:

<?xml version=„1.0“ encoding=„UTF-8“?>
<
process-definition xmlns=„urn:jbpm.org:jpdl-3.2“ name=„quarantine.process“>
<
start-state name=„start-state1“>
<
transition to=„listOrgs“/>
</
start-state>
<
node name=„listOrgs“>
<
event type=„node-enter“>
<
script>
<
expression>
import eu.bcvsolutions.idm.data.Data;
import eu.bcvsolutions.idm.app.Application;
import eu.bcvsolutions.idm.data.dto.Criteria;
import eu.bcvsolutions.idm.data.view.View;
//vsechny organizace v CzechIdM
System.out.println(„quarantine.process – started“);
List orgs = Data.listIds(View.Type.ORGANISATION, new Criteria()) //Tady chybi strednik!
System.out.println(„quarantine.process – orgs: “ + orgs);

</expression>
<
variable name=„orgs“ access=„write“/>
</
script>
</
event>
<
transition to=„end-state1“/>
</
node>
<
end-state name=„end-state1“/>

</process-definition>

Jak jste si možná všimli, kdesi hluboko v XML začíná cosi, co se nápadně podobá Javě. Java to ovšem není, je to z ní odvozený skriptovací jazyk BeanShell. Pokud jste se dívali pozorně, možná jste si všimli i toho chybějícího středníku na řádku 8. V dalším odstavci vám ukážu, jak ho pomocí Javy najít…

Třídy bsh.Interpreter a bsh.Parser

V balíku bsh.jar jsou k mání dvě třídy pro práci s BeanShell skripty: bsh.Interpreter a bsh.Parser. První jmenovaná se používá ke spouštění BeanShell skriptů z Javy a interně používá druhou. Intepreter nám ovšem při verifikaci kódu bez jeho spuštění příliš nepomůže. Proto použijeme Parser, který umí rozsekat BeanShell skript na jednotlivé tokeny a na případnou chybu v syntaxi zareagovat výjimkou.

Program

Nejprve z jPDL souboru pomocí JDOM knihovny pro práci s XML vyřízneme všechny BeanShell skripty a každý z nich necháme zkontrolovat bsh.Parserem. Pokud při parsování dojde k chybě, vypíšeme ji na standardní výstup.

private static void checkXMLFile(File f) {
System.
out.println(„Processing file „ + f.getParentFile().getName() + „/“ + f.getName());
   try {
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(f);
doc.getDocumentElement().normalize();

for (String expressionNode : expressionNodes) {
NodeList exprNodes = doc.getElementsByTagName(expressionNode);
for (int s = 0; s < exprNodes.getLength(); s++) {
Node fstNode = exprNodes.item(s);
             String nodeName = fstNode.getAttributes().getNamedItem(„name“).getNodeValue();
List
nodeList = findSubNodes(fstNode, „expression“);
checkNodeListValue
(nodeList, f.getParentFile().getName() + „/“+ f.getName(), nodeName);
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
System.
out.println(„Error while parsing xml: „ + e.getMessage());

} catch (SAXException e) {
e.printStackTrace();
}
}

private static void checkNodeListValue(NodeList nodeList, String fileName, String nodeName) {
   for (int s = 0; s < nodeList.getLength(); s++) {
       Node fstNode = nodeList.item(s);
NodeList lstNm = fstNode.getChildNodes();
String scriptToParse = ((Node) lstNm.item(0)).getNodeValue();
checkScript(scriptToParse, fileName, nodeName);
}
}

privatestaticvoid checkScript(String s, String fileName, String nodeName) {
      if (isEmpty(s)) {
           warnings.add(„WARNING: „ + fileName + „: whitespace script expression?“);
           return;
}
Parser parser = new Parser(new StringReader(s));
do {
try {
if (parser.Line()) {
break;
}
} catch (ParseException e) {
errors.add(„ERROR: „ + fileName + „: „ + e.getMessage());
return;
}
} while (true);
}

Zkusme naši aplikaci spustit na onom workflow s chybějícím středníkem:

ERROR: quarantine.process/processdefinition.xml: Parse error at line 19, column 41.  Encountered: System

Závěr

V článku jsem nastínil, jak by mohla vypadat jednoduchá aplikace, která verifikuje syntaxi BeanShellu schovaného v jPDL. Moje aplikace, kterou jsem zmínil v úvodu, toho umí ještě víc: odhalí chybějící importy, pozná volání neexistujících metod, přístupy k neexistujícím atributům nebo nedeklarovaným proměnným. To by ovšem bylo na delší povídání :-). Kdybyste se chtěli na cokoli zeptat nebo kdybyste náhodou věděli o existujícím jPDL pluginu do Eclipse, napište mi na vojtech.matocha@bcvsolutions.eu!