Java tutorial
/* Copyright (C) 2006-2007 Helge Hess This file is part of Go. Go is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Go is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with Go; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.getobjects.rules; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.getobjects.eocontrol.EOAndQualifier; import org.getobjects.eocontrol.EOBooleanQualifier; import org.getobjects.eocontrol.EOQualifier; import org.getobjects.foundation.NSClassLookupContext; import org.getobjects.foundation.NSObject; import org.getobjects.foundation.UObject; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * RuleModelLoader * <p> * Loads a RuleModel from an XML file. * * Sample: * <pre><code> * <?xml version="1.0"?> * <model version="1.0"> * * <rule priority="high"> * <qualifier>*true*</qualifier> * <key>color</key> * <value>'green'</value> * </rule> * * <rule priority="high"> * <qualifier>*true*</qualifier> * <key>color</key> * <var:value>backgroundColor</var:value> * </rule> * * <rule priority="low"> * <qualifier>*true*</qualifier> * <action>color = backgroundColor</action> * </rule> * * <rule>*true* => color = 'green'; high</rule> * * </model> * </code></pre> * * Tag Aliases: * <pre> * qualifier - q * action - a * priority - p * </pre> * * TBD: multirule * * @see RuleModel */ public class RuleModelLoader extends NSObject { protected static final Log log = LogFactory.getLog("GoRuleModelLoader"); /* statics */ protected static DocumentBuilderFactory dbf; static { dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(false); /* we directly deal with prefixes */ dbf.setCoalescing(true); /* join adjacent texts */ dbf.setIgnoringComments(true); } /* ivars */ protected NSClassLookupContext classLookup; protected RuleParser ruleParser; protected Exception lastException; public RuleModelLoader(NSClassLookupContext _clslookup) { this.classLookup = _clslookup; this.ruleParser = new RuleParser(_clslookup); } public RuleModelLoader() { this(NSClassLookupContext.NSSystemClassLookupContext); } /* accessors */ public Exception lastException() { return this.lastException; } public void clear() { this.lastException = null; } /* loading the model */ protected RuleModel loadModelFromElement(Element _node) { NodeList children = _node.getElementsByTagName("rule"); int ruleCount = children != null ? children.getLength() : 0; List<Rule> rules = new ArrayList<Rule>(ruleCount); /* load regular rules */ for (int i = 0; i < ruleCount; i++) { Rule rule = this.loadRuleFromElement((Element) children.item(i)); if (rule == null) { log.error("got no rule for element: " + children.item(i)); continue; } rules.add(rule); } /* load multi rules */ if ((children = _node.getElementsByTagName("multirule")) != null) { ruleCount = children.getLength(); for (int i = 0; i < ruleCount; i++) { Rule[] mrules = this.loadMultiRulesFromElement((Element) children.item(i)); if (mrules == null) continue; for (int j = 0; j < mrules.length; j++) rules.add(mrules[j]); } } return new RuleModel(rules.toArray(new Rule[0])); } protected RuleModel loadModelFromDocument(Document _doc) { return this.loadModelFromElement(_doc.getDocumentElement()); } public Rule loadRuleFromElement(Element _node) { if (_node == null) return null; boolean debugOn = log.isDebugEnabled(); /* try to load structured rule setup (tags for rule definition) */ EOQualifier sq = this.loadQualifiersOfRule(_node); RuleAction sa = this.loadActionOfRule(_node); Integer sp = this.loadPriorityOfRule(_node); String sk = null; if (sa == null) { sk = this.joinTrimmedTextsOfElements(_node.getElementsByTagName("key"), "." /* build keypath (useless) */); } /* check whether its structured */ if (sq == null && sa == null && sk == null) { /* not structured */ String s = _node.getTextContent(); if (s == null || s.length() == 0) { log.error("found rule tag w/o recognisable content"); return null; } if (sp != null) s = s + "; " + sp; // TODO: check for dups Rule rule = this.ruleParser.parseRule(s); if (rule == null) log.error("could not parse rule: '" + s + "'"); return rule; } /* treat as a structured rule */ if (debugOn) log.debug("load structured rule, sk: " + sk + ", sa: " + sa); if (sk != null && sa != null) log.warn("rule has both, 'key' and 'action' tags. Using 'action'."); else if (sk != null) { sa = this.loadSplitRuleAction(_node, sk); if (debugOn) log.debug(" parsed split rule: " + sa); } if (sq == null) /* no qualifier given, using *true* */ sq = EOBooleanQualifier.trueQualifier; if (sp == null) /* no priority given, use normal */ sp = new Integer(RuleParser.RULE_PRIORITY_NORMAL); return new Rule(sq, sa, sp.intValue()); } public Rule[] loadMultiRulesFromElement(Element _node) { if (_node == null) return null; /* try to load structured rule setup (tags for rule definition) */ RuleAction sa = this.loadActionOfRule(_node); Integer sp = this.loadPriorityOfRule(_node); String sk = null; if (sa == null) { sk = this.joinTrimmedTextsOfElements(_node.getElementsByTagName("key"), "." /* build keypath (useless) */); } /* treat as a structured rule */ if (sk != null && sa != null) log.warn("multirule has both, 'key' and 'action' tags. Using 'action'."); else if (sk != null) sa = this.loadSplitRuleAction(_node, sk); if (sp == null) /* no priority given, use normal */ sp = new Integer(RuleParser.RULE_PRIORITY_NORMAL); /* walk over qualifiers, generate a rule for each */ NodeList children = _node.getElementsByTagName("qualifier"); if (children == null) children = _node.getElementsByTagName("q"); if (children == null || children.getLength() == 0) { log.warn("multirule tag has no qualifiers?"); return null; } Rule[] qs = new Rule[children.getLength()]; for (int i = 0; i < qs.length; i++) { EOQualifier q = EOQualifier.qualifierWithQualifierFormat(children.item(i).getTextContent(), noArgs); qs[i] = new Rule(q, sa, sp.intValue()); } return qs; } protected static Object[] noArgs = {}; public EOQualifier loadQualifiersOfRule(Element _node) { if (_node == null) return null; NodeList children = _node.getElementsByTagName("qualifier"); if (children == null) children = _node.getElementsByTagName("q"); if (children == null || children.getLength() == 0) return null; /* has no qualifier subelements */ EOQualifier[] qs = new EOQualifier[children.getLength()]; for (int i = 0; i < qs.length; i++) { qs[i] = EOQualifier.qualifierWithQualifierFormat(children.item(i).getTextContent(), noArgs); } if (qs.length == 1) return qs[0]; return new EOAndQualifier(qs); } public RuleAction loadActionOfRule(Element _node) { if (_node == null) return null; NodeList children = _node.getElementsByTagName("action"); if (children == null) children = _node.getElementsByTagName("a"); if (children == null || children.getLength() == 0) return null; /* has no action subelements */ RuleAction[] as = new RuleAction[children.getLength()]; for (int i = 0; i < as.length; i++) { Element child = (Element) children.item(i); String actionClassName = child.getAttribute("class"); as[i] = (RuleAction) this.ruleParser.parseAction(child.getTextContent(), actionClassName); } return CompoundRuleAction.ruleActionForActionArray(as); } public RuleAction loadSplitRuleAction(Element _node, String _keyPath) { if (_node == null) return null; /* first check for constant values */ NodeList children = _node.getElementsByTagName("value"); if (children == null || children.getLength() == 0) children = _node.getElementsByTagName("v"); if (children != null && children.getLength() > 0) { String v = this.joinTrimmedTextsOfElements(children, ""); // System.err.println("X: " + v + ": " + children); return new RuleAssignment(_keyPath, v); } /* check for variable values */ children = _node.getElementsByTagName("var:value"); if (children == null || children.getLength() == 0) children = _node.getElementsByTagName("var:v"); if (children != null && children.getLength() > 0) { String v = this.joinTrimmedTextsOfElements(children, "." /* keypath */); return new RuleKeyAssignment(_keyPath, v); } /* didn't find a value */ log.warn("did not find value tag in rule for keypath: " + _keyPath); return null; } public Integer loadPriorityOfRule(Element _node) { if (_node == null) return null; /* check for attribute */ String s = _node.getAttribute("priority"); if (s != null && s.length() > 0) return priorityForString(s); /* check for subtag */ NodeList children = _node.getElementsByTagName("priority"); if (children == null) children = _node.getElementsByTagName("p"); if (children == null || children.getLength() == 0) return null; /* has no action subelements */ if (children.getLength() > 1) log.error("multiple priorities given for rule!"); return priorityForString(children.item(0).getTextContent()); } public static Integer priorityForString(String _s) { if (_s == null) return null; if (Character.isDigit(_s.charAt(0))) return new Integer(UObject.intValue(_s)); return RuleParser.parsePriority(_s); } /* support */ protected Exception newModelLoadingException(String _reason) { // TODO: improve error handling return new Exception(_reason); } protected void addError(String _reason) { log.error(_reason); this.lastException = this.newModelLoadingException(_reason); } protected void addError(String _reason, Exception _e) { log.error(_reason, _e); // TODO: wrap exception this.lastException = _e; } public RuleModel loadModelFromURL(URL _url) { boolean isDebugOn = log.isDebugEnabled(); if (isDebugOn) log.debug("loading model from URL: " + _url); if (_url == null) { this.addError("missing URL parameter for loading model"); return null; } /* instantiate document builder */ DocumentBuilder db; try { db = dbf.newDocumentBuilder(); if (isDebugOn) log.debug(" using DOM document builder:" + db); } catch (ParserConfigurationException e) { this.addError("failed to create docbuilder for parsing URL: " + _url, e); return null; } /* load DOM */ Document doc; try { doc = db.parse(_url.openStream(), _url.toString()); if (isDebugOn) log.debug(" parsed DOM: " + doc); } catch (SAXException e) { this.addError("XML error when loading model resource: " + _url, e); return null; } catch (IOException e) { this.addError("IO error when loading model resource: " + _url, e); return null; } /* transform DOM into model */ RuleModel model = this.loadModelFromDocument(doc); if (isDebugOn && model != null) { log.debug(" model: " + model); log.debug("finished model from URL: " + _url); } if (model == null) log.info("failed loading model from URL: " + _url); return model; } protected String joinTrimmedTextsOfElements(NodeList _nodes, String _sep) { if (_nodes == null || _nodes.getLength() == 0) return null; StringBuilder sb = new StringBuilder(256); boolean isFirst = true; for (int i = 0; i < _nodes.getLength(); i++) { Element node = (Element) _nodes.item(i); node.normalize(); String txt = node.getTextContent(); if (txt == null) continue; txt = txt.trim(); if (txt.length() == 0) continue; if (isFirst) isFirst = false; else if (_sep != null) sb.append(_sep); sb.append(txt); } return sb.length() > 0 ? sb.toString() : null; } }