001/*
002 * JPPF.
003 * Copyright (C) 2005-2019 JPPF Team.
004 * http://www.jppf.org
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.jppf.node.policy;
020
021import java.io.*;
022import java.util.*;
023
024import javax.xml.parsers.*;
025
026import org.jppf.JPPFException;
027import org.jppf.utils.*;
028import org.w3c.dom.*;
029import org.xml.sax.InputSource;
030
031/**
032 * This class is a parser for XML Execution Policy documents.
033 * @author Laurent Cohen
034 */
035public class PolicyParser {
036  /**
037   * List of possible rule names.
038   */
039  private static final List<String> RULE_NAMES = Arrays.asList("NOT", "AND", "OR", "XOR", "LessThan", "AtMost", "AtLeast", "MoreThan", "BetweenII",
040    "BetweenIE", "BetweenEI", "BetweenEE", "Equal", "Contains", "OneOf", "RegExp", "CustomRule", "Script", "Preference", "IsInIPv4Subnet", "IsInIPv6Subnet",
041    NodesMatching.XML_TAG, AcceptAll.XML_TAG, RejectAll.XML_TAG, "IsMasterNode", "IsSlaveNode", "IsLocalChannel", "IsPeerDriver");
042  /**
043   * The set of policy rules that take no argument.
044   */
045  private static Set<String> RULES_WITHOUT_ARGUMENT = new HashSet<>(Arrays.asList("IsMasterNode", "IsSlaveNode"));
046  /**
047   * The DOM parser used to build the descriptor tree.
048   */
049  private DocumentBuilder parser = null;
050
051  /**
052   * Initialize this parser.
053   * @throws Exception if the DOM parser could not be initialized.
054   */
055  public PolicyParser() throws Exception {
056    final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
057    parser = dbf.newDocumentBuilder();
058  }
059
060  /**
061   * Parse an XML document in a file into a tree of option descriptors.
062   * @param docPath the path to XML document to parse.
063   * @return an <code>OptionDescriptor</code> instance, root of the generated tree,
064   * or null if the document could not be parsed.
065   * @throws Exception if an error occurs while parsing the document.
066   */
067  public PolicyDescriptor parse(final String docPath) throws Exception {
068    final InputStream is = FileUtils.getFileInputStream(docPath);
069    if (is == null) throw new JPPFException("could not find document at " + docPath);
070    final Document doc = parser.parse(is);
071    return generateTree(findFirstElement(doc));
072  }
073
074  /**
075   * Parse an XML document in a reader into a tree of option descriptors.
076   * @param reader the reader providing the XML document.
077   * @return an <code>OptionDescriptor</code> instance, root of the generated tree,
078   * or null if the document could not be parsed.
079   * @throws Exception if an error occurs while parsing the document.
080   */
081  public PolicyDescriptor parse(final Reader reader) throws Exception {
082    final InputSource is = new InputSource(reader);
083    final Document doc = parser.parse(is);
084    return generateTree(findFirstElement(doc));
085  }
086
087  /**
088   * Find the first node in a document that is an element node.
089   * @param doc the document whose children are looked up.
090   * @return a <code>Node</code> instance if one was found, or null otherwise.
091   */
092  private static Node findFirstElement(final Document doc) {
093    final NodeList list = doc.getChildNodes();
094    for (int i=0; i<list.getLength(); i++) {
095      final Node node = list.item(i);
096      if (node.getNodeType() == Node.ELEMENT_NODE) return node;
097    }
098    return null;
099  }
100
101  /**
102   * Generate an <code>PolicyDescriptor</code> tree from a DOM subtree.
103   * @param node the document to generate the tree from.
104   * @return an <code>OptionDescriptor</code> instance, root of the generated tree,
105   * or null if the document could not be parsed.
106   */
107  private PolicyDescriptor generateTree(final Node node) {
108    final PolicyDescriptor desc = new PolicyDescriptor();
109    desc.type = node.getNodeName();
110    if ("Script".equals(desc.type)) desc.script = getTextNodeValue(node);
111    desc.valueType = getAttributeValue(node, "valueType", "string");
112    desc.ignoreCase = getAttributeValue(node, "ignoreCase", "false");
113    desc.className = getAttributeValue(node, "class", null);
114    desc.language = getAttributeValue(node, "language", null);
115    desc.operator = getAttributeValue(node, "operator", "EQUAL");
116    desc.expected = getAttributeValue(node, "expected", "0");
117    final NodeList list = node.getChildNodes();
118    for (int i=0; i<list.getLength(); i++) {
119      final Node childNode = list.item(i);
120      if (childNode.getNodeType() == Node.ELEMENT_NODE) {
121        final String name = childNode.getNodeName();
122        if (RULE_NAMES.contains(name)) desc.children.add(generateTree(childNode));
123        else if ("Property".equals(name) || "Value".equals(name) || "Subnet".equals(name)) desc.operands.add(getTextNodeValue(childNode));
124        else if ("Arg".equals(name)) desc.arguments.add(getTextNodeValue(childNode));
125      }
126    }
127    return desc;
128  }
129
130  /**
131   * Get the value of a node's text subelement.
132   * @param node the node to generate whose child is a text node.
133   * @return the text as a string.
134   */
135  private static String getTextNodeValue(final Node node) {
136    final NodeList children = node.getChildNodes();
137    for (int j=0; j<children.getLength(); j++) {
138      final Node childNode = children.item(j);
139      final int type = childNode.getNodeType();
140      if ((type == Node.TEXT_NODE) || (type == Node.CDATA_SECTION_NODE)) return childNode.getNodeValue();
141    }
142    return null;
143  }
144
145  /**
146   * Get the value of the attriobute of a node.
147   * @param node the node from which to get the attribute.
148   * @param name the name of the attribute to get.
149   * @param def the default value to use if the attribute is not defined.
150   * @return the attribute value as a string.
151   */
152  private static String getAttributeValue(final Node node, final String name, final String def) {
153    final NamedNodeMap attrMap = node.getAttributes();
154    final Node attrNode = attrMap.getNamedItem(name);
155    return attrNode == null ? def : attrNode.getNodeValue();
156  }
157
158  /**
159   * Test of the parser.
160   * @param args not used.
161   */
162  public static void main(final String...args) {
163    try {
164      final String docPath = "ExecutionPolicy.xml";
165      final JPPFErrorReporter reporter = new JPPFErrorReporter(docPath);
166      final String schemaPath = "org/jppf/schemas/ExecutionPolicy.xsd";
167      final SchemaValidator validator = new SchemaValidator(reporter);
168      if (!validator.validate(docPath, schemaPath)) {
169        final String s = "the document " + docPath;
170        System.out.println(s + " has errors.");
171        System.out.println("fatal errors: " + reporter.allFatalErrorsAsStrings());
172        System.out.println("errors      : " + reporter.allErrorsAsStrings());
173        System.out.println("warnings    : " + reporter.allWarningsAsStrings());
174        return;
175      }
176      final PolicyParser parser = new PolicyParser();
177      final PolicyDescriptor desc = parser.parse(docPath);
178      final ExecutionPolicy policy = new PolicyBuilder().buildPolicy(desc.children.get(0));
179      System.out.println("Successfully build policy object:\n" + policy);
180    } catch(final Exception e) {
181      e.printStackTrace();
182    }
183  }
184
185  /**
186   * Parse an XML document representing an execution policy.
187   * @param policyContent an XML string containing the policy.
188   * @return an <code>ExecutionPolicy</code> instance.
189   * @throws Exception if an error occurs during the validation or parsing.
190   */
191  public static ExecutionPolicy parsePolicy(final String policyContent) throws Exception {
192    return parsePolicy(new StringReader(policyContent));
193  }
194
195  /**
196   * Parse an XML document representing an execution policy from a file path.
197   * @param docPath path to the XML document file.
198   * @return an <code>ExecutionPolicy</code> instance.
199   * @throws Exception if an error occurs during the validation or parsing.
200   */
201  public static ExecutionPolicy parsePolicyFile(final String docPath) throws Exception {
202    return parsePolicy(FileUtils.getFileReader(docPath));
203  }
204
205  /**
206   * Parse an XML document representing an execution policy from a file path.
207   * @param policyFile abstract path of the XML document file.
208   * @return an <code>ExecutionPolicy</code> instance.
209   * @throws Exception if an error occurs during the validation or parsing.
210   */
211  public static ExecutionPolicy parsePolicy(final File policyFile) throws Exception {
212    return parsePolicy(new BufferedReader(new FileReader(policyFile)));
213  }
214
215  /**
216   * Parse an XML document representing an execution policy from an input stream.
217   * @param stream an input stream from which the XML representation of the policy is read.
218   * @return an <code>ExecutionPolicy</code> instance.
219   * @throws Exception if an error occurs during the validation or parsing.
220   */
221  public static ExecutionPolicy parsePolicy(final InputStream stream) throws Exception {
222    return parsePolicy(new InputStreamReader(stream));
223  }
224
225  /**
226   * Parse an XML document representing an execution policy from a reader.
227   * @param reader reader from which the XML representation of the policy is read.
228   * @return an <code>ExecutionPolicy</code> instance.
229   * @throws Exception if an error occurs during the validation or parsing.
230   */
231  public static ExecutionPolicy parsePolicy(final Reader reader) throws Exception {
232    final PolicyDescriptor desc = new PolicyParser().parse(reader);
233    if (desc.children.isEmpty() && !RULES_WITHOUT_ARGUMENT.contains(desc.type)) throw new JPPFException("empty execution policy");
234    return new PolicyBuilder().buildPolicy(desc.children.get(0));
235  }
236
237  /**
238   * Validate an XML document representing an execution policy against the
239   * <a href="http://www.jppf.org/schemas/ExecutionPolicy.xsd">JPPF Execution Policy schema</a>.
240   * @param policyContent the XML content of the policy document.
241   * @throws JPPFException if there is a validation error. The details of the errors are included in the exception message.
242   * @throws Exception if an error occurs during the validation.
243   */
244  public static void validatePolicy(final String policyContent) throws JPPFException, Exception {
245    try (Reader reader = new StringReader(policyContent)) {
246      validatePolicy(reader);
247    }
248  }
249
250  /**
251   * Validate an XML document representing an execution policy against the
252   * <a href="http://www.jppf.org/schemas/ExecutionPolicy.xsd">JPPF Execution Policy schema</a>.
253   * @param docPath path to the XML document file.
254   * @throws JPPFException if there is a validation error. The details of the errors are included in the exception message.
255   * @throws Exception if an error occurs during the validation.
256   */
257  public static void validatePolicyFile(final String docPath) throws JPPFException, Exception {
258    validatePolicy(FileUtils.getFileReader(docPath));
259  }
260
261  /**
262   * Validate an XML document representing an execution policy against the
263   * <a href="http://www.jppf.org/schemas/ExecutionPolicy.xsd">JPPF Execution Policy schema</a>.
264   * @param docPath abstract path of the XML document file.
265   * @throws JPPFException if there is a validation error. The details of the errors are included in the exception message.
266   * @throws Exception if an error occurs during the validation.
267   */
268  public static void validatePolicy(final File docPath) throws JPPFException, Exception {
269    validatePolicy(new BufferedReader(new FileReader(docPath)));
270  }
271
272  /**
273   * Validate an XML document representing an execution policy against the
274   * <a href="http://www.jppf.org/schemas/ExecutionPolicy.xsd">JPPF Execution Policy schema</a>.
275   * @param stream an input stream from which the XML representation of the policy is read.
276   * @throws JPPFException if there is a validation error. The details of the errors are included in the exception message.
277   * @throws Exception if an error occurs during the validation or parsing.
278   */
279  public static void validatePolicy(final InputStream stream) throws JPPFException, Exception {
280    validatePolicy(new InputStreamReader(stream));
281  }
282
283  /**
284   * Validate an XML document representing an execution policy against the
285   * <a href="http://www.jppf.org/schemas/ExecutionPolicy.xsd">JPPF Execution Policy schema</a>.
286   * @param reader reader from which the XML representation of the policy is read.
287   * @throws JPPFException if there is a validation error. The details of the errors are included in the exception message.
288   * @throws Exception if an error occurs during the validation or parsing.
289   */
290  public static void validatePolicy(final Reader reader) throws JPPFException, Exception {
291    final JPPFErrorReporter reporter = new JPPFErrorReporter("XML validator");
292    final String schemaPath = "org/jppf/schemas/ExecutionPolicy.xsd";
293    final SchemaValidator validator = new SchemaValidator(reporter);
294    if (!validator.validate(reader, FileUtils.getFileReader(schemaPath))) {
295      final StringBuilder sb = new StringBuilder();
296      //sb.append("The XML document has errors:\n");
297      if (!reporter.fatalErrors.isEmpty()) sb.append("fatal errors: ").append(reporter.allFatalErrorsAsStrings()).append('\n');
298      if (!reporter.errors.isEmpty())      sb.append("errors      : ").append(reporter.allErrorsAsStrings()).append('\n');
299      if (!reporter.warnings.isEmpty())    sb.append("warnings    : ").append(reporter.allWarningsAsStrings()).append('\n');
300      throw new JPPFException(sb.toString());
301    }
302  }
303}