001/*
002 * JPPF.
003 * Copyright (C) 2005-2018 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",
040      "BetweenII", "BetweenIE", "BetweenEI", "BetweenEE", "Equal", "Contains", "OneOf", "RegExp", "CustomRule", "Script", "Preference",
041      "IsInIPv4Subnet", "IsInIPv6Subnet", NodesMatching.XML_TAG, AcceptAll.XML_TAG, RejectAll.XML_TAG);
042  /**
043   * The DOM parser used to build the descriptor tree.
044   */
045  private DocumentBuilder parser = null;
046
047  /**
048   * Initialize this parser.
049   * @throws Exception if the DOM parser could not be initialized.
050   */
051  public PolicyParser() throws Exception {
052    final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
053    parser = dbf.newDocumentBuilder();
054  }
055
056  /**
057   * Parse an XML document in a file into a tree of option descriptors.
058   * @param docPath the path to XML document to parse.
059   * @return an <code>OptionDescriptor</code> instance, root of the generated tree,
060   * or null if the document could not be parsed.
061   * @throws Exception if an error occurs while parsing the document.
062   */
063  public PolicyDescriptor parse(final String docPath) throws Exception {
064    final InputStream is = FileUtils.getFileInputStream(docPath);
065    if (is == null) return null;
066    final Document doc = parser.parse(is);
067    return generateTree(findFirstElement(doc));
068  }
069
070  /**
071   * Parse an XML document in a reader into a tree of option descriptors.
072   * @param reader the reader providing the XML document.
073   * @return an <code>OptionDescriptor</code> instance, root of the generated tree,
074   * or null if the document could not be parsed.
075   * @throws Exception if an error occurs while parsing the document.
076   */
077  public PolicyDescriptor parse(final Reader reader) throws Exception {
078    final InputSource is = new InputSource(reader);
079    final Document doc = parser.parse(is);
080    return generateTree(findFirstElement(doc));
081  }
082
083  /**
084   * Find the first node in a document that is an element node.
085   * @param doc the document whose children are looked up.
086   * @return a <code>Node</code> instance if one was found, or null otherwise.
087   */
088  private static Node findFirstElement(final Document doc) {
089    final NodeList list = doc.getChildNodes();
090    for (int i=0; i<list.getLength(); i++) {
091      final Node node = list.item(i);
092      if (node.getNodeType() == Node.ELEMENT_NODE) return node;
093    }
094    return null;
095  }
096
097  /**
098   * Generate an <code>PolicyDescriptor</code> tree from a DOM subtree.
099   * @param node the document to generate the tree from.
100   * @return an <code>OptionDescriptor</code> instance, root of the generated tree,
101   * or null if the document could not be parsed.
102   */
103  private PolicyDescriptor generateTree(final Node node) {
104    final PolicyDescriptor desc = new PolicyDescriptor();
105    desc.type = node.getNodeName();
106    if ("Script".equals(desc.type)) desc.script = getTextNodeValue(node);
107    desc.valueType = getAttributeValue(node, "valueType", "string");
108    desc.ignoreCase = getAttributeValue(node, "ignoreCase", "false");
109    desc.className = getAttributeValue(node, "class", null);
110    desc.language = getAttributeValue(node, "language", null);
111    desc.operator = getAttributeValue(node, "operator", "EQUAL");
112    desc.expected = getAttributeValue(node, "expected", "0");
113    final NodeList list = node.getChildNodes();
114    for (int i=0; i<list.getLength(); i++) {
115      final Node childNode = list.item(i);
116      if (childNode.getNodeType() == Node.ELEMENT_NODE) {
117        final String name = childNode.getNodeName();
118        /*if ("Script".equals(name)) desc.script = getTextNodeValue(childNode);
119        else*/ if (RULE_NAMES.contains(name)) desc.children.add(generateTree(childNode));
120        else if ("Property".equals(name) || "Value".equals(name) || "Subnet".equals(name)) desc.operands.add(getTextNodeValue(childNode));
121        else if ("Arg".equals(name)) desc.arguments.add(getTextNodeValue(childNode));
122      }
123    }
124    return desc;
125  }
126
127  /**
128   * Get the value of a node's text subelement.
129   * @param node the node to generate whose child is a text node.
130   * @return the text as a string.
131   */
132  private static String getTextNodeValue(final Node node) {
133    final NodeList children = node.getChildNodes();
134    for (int j=0; j<children.getLength(); j++) {
135      final Node childNode = children.item(j);
136      final int type = childNode.getNodeType();
137      if ((type == Node.TEXT_NODE) || (type == Node.CDATA_SECTION_NODE)) return childNode.getNodeValue();
138    }
139    return null;
140  }
141
142  /**
143   * Get the value of the attriobute of a node.
144   * @param node the node from which to get the attribute.
145   * @param name the name of the attribute to get.
146   * @param def the default value to use if the attribute is not defined.
147   * @return the attribute value as a string.
148   */
149  private static String getAttributeValue(final Node node, final String name, final String def) {
150    final NamedNodeMap attrMap = node.getAttributes();
151    final Node attrNode = attrMap.getNamedItem(name);
152    return attrNode == null ? def : attrNode.getNodeValue();
153  }
154
155  /**
156   * Test of the parser.
157   * @param args not used.
158   */
159  public static void main(final String...args) {
160    try {
161      final String docPath = "ExecutionPolicy.xml";
162      final JPPFErrorReporter reporter = new JPPFErrorReporter(docPath);
163      final String schemaPath = "org/jppf/schemas/ExecutionPolicy.xsd";
164      final SchemaValidator validator = new SchemaValidator(reporter);
165      if (!validator.validate(docPath, schemaPath)) {
166        final String s = "the document " + docPath;
167        System.out.println(s + " has errors.");
168        System.out.println("fatal errors: " + reporter.allFatalErrorsAsStrings());
169        System.out.println("errors      : " + reporter.allErrorsAsStrings());
170        System.out.println("warnings    : " + reporter.allWarningsAsStrings());
171        return;
172      }
173      final PolicyParser parser = new PolicyParser();
174      final PolicyDescriptor desc = parser.parse(docPath);
175      final ExecutionPolicy policy = new PolicyBuilder().buildPolicy(desc.children.get(0));
176      System.out.println("Successfully build policy object:\n" + policy);
177    } catch(final Exception e) {
178      e.printStackTrace();
179    }
180  }
181
182  /**
183   * Parse an XML document representing an execution policy.
184   * @param policyContent an XML string containing the policy.
185   * @return an <code>ExecutionPolicy</code> instance.
186   * @throws Exception if an error occurs during the validation or parsing.
187   */
188  public static ExecutionPolicy parsePolicy(final String policyContent) throws Exception {
189    return parsePolicy(new StringReader(policyContent));
190  }
191
192  /**
193   * Parse an XML document representing an execution policy from a file path.
194   * @param docPath path to the XML document file.
195   * @return an <code>ExecutionPolicy</code> instance.
196   * @throws Exception if an error occurs during the validation or parsing.
197   */
198  public static ExecutionPolicy parsePolicyFile(final String docPath) throws Exception {
199    return parsePolicy(FileUtils.getFileReader(docPath));
200  }
201
202  /**
203   * Parse an XML document representing an execution policy from a file path.
204   * @param policyFile abstract path of the XML document file.
205   * @return an <code>ExecutionPolicy</code> instance.
206   * @throws Exception if an error occurs during the validation or parsing.
207   */
208  public static ExecutionPolicy parsePolicy(final File policyFile) throws Exception {
209    return parsePolicy(new BufferedReader(new FileReader(policyFile)));
210  }
211
212  /**
213   * Parse an XML document representing an execution policy from an input stream.
214   * @param stream an input stream from which the XML representation of the policy is read.
215   * @return an <code>ExecutionPolicy</code> instance.
216   * @throws Exception if an error occurs during the validation or parsing.
217   */
218  public static ExecutionPolicy parsePolicy(final InputStream stream) throws Exception {
219    return parsePolicy(new InputStreamReader(stream));
220  }
221
222  /**
223   * Parse an XML document representing an execution policy from a reader.
224   * @param reader reader from which the XML representation of the policy is read.
225   * @return an <code>ExecutionPolicy</code> instance.
226   * @throws Exception if an error occurs during the validation or parsing.
227   */
228  public static ExecutionPolicy parsePolicy(final Reader reader) throws Exception {
229    final PolicyDescriptor desc = new PolicyParser().parse(reader);
230    if (desc.children.isEmpty()) throw new JPPFException("empty execution policy");
231    return new PolicyBuilder().buildPolicy(desc.children.get(0));
232  }
233
234  /**
235   * Validate an XML document representing an execution policy against the
236   * <a href="http://www.jppf.org/schemas/ExecutionPolicy.xsd">JPPF Execution Policy schema</a>.
237   * @param policyContent the XML content of the policy document.
238   * @throws JPPFException if there is a validation error. The details of the errors are included in the exception message.
239   * @throws Exception if an error occurs during the validation.
240   */
241  public static void validatePolicy(final String policyContent) throws JPPFException, Exception {
242    try (Reader reader = new StringReader(policyContent)) {
243      validatePolicy(reader);
244    }
245  }
246
247  /**
248   * Validate an XML document representing an execution policy against the
249   * <a href="http://www.jppf.org/schemas/ExecutionPolicy.xsd">JPPF Execution Policy schema</a>.
250   * @param docPath path to the XML document file.
251   * @throws JPPFException if there is a validation error. The details of the errors are included in the exception message.
252   * @throws Exception if an error occurs during the validation.
253   */
254  public static void validatePolicyFile(final String docPath) throws JPPFException, Exception {
255    validatePolicy(FileUtils.getFileReader(docPath));
256  }
257
258  /**
259   * Validate an XML document representing an execution policy against the
260   * <a href="http://www.jppf.org/schemas/ExecutionPolicy.xsd">JPPF Execution Policy schema</a>.
261   * @param docPath abstract path of the XML document file.
262   * @throws JPPFException if there is a validation error. The details of the errors are included in the exception message.
263   * @throws Exception if an error occurs during the validation.
264   */
265  public static void validatePolicy(final File docPath) throws JPPFException, Exception {
266    validatePolicy(new BufferedReader(new FileReader(docPath)));
267  }
268
269  /**
270   * Validate an XML document representing an execution policy against the
271   * <a href="http://www.jppf.org/schemas/ExecutionPolicy.xsd">JPPF Execution Policy schema</a>.
272   * @param stream an input stream from which the XML representation of the policy is read.
273   * @throws JPPFException if there is a validation error. The details of the errors are included in the exception message.
274   * @throws Exception if an error occurs during the validation or parsing.
275   */
276  public static void validatePolicy(final InputStream stream) throws JPPFException, Exception {
277    validatePolicy(new InputStreamReader(stream));
278  }
279
280  /**
281   * Validate an XML document representing an execution policy against the
282   * <a href="http://www.jppf.org/schemas/ExecutionPolicy.xsd">JPPF Execution Policy schema</a>.
283   * @param reader reader from which the XML representation of the policy is read.
284   * @throws JPPFException if there is a validation error. The details of the errors are included in the exception message.
285   * @throws Exception if an error occurs during the validation or parsing.
286   */
287  public static void validatePolicy(final Reader reader) throws JPPFException, Exception {
288    final JPPFErrorReporter reporter = new JPPFErrorReporter("XML validator");
289    final String schemaPath = "org/jppf/schemas/ExecutionPolicy.xsd";
290    final SchemaValidator validator = new SchemaValidator(reporter);
291    if (!validator.validate(reader, FileUtils.getFileReader(schemaPath))) {
292      final StringBuilder sb = new StringBuilder();
293      //sb.append("The XML document has errors:\n");
294      if (!reporter.fatalErrors.isEmpty()) sb.append("fatal errors: ").append(reporter.allFatalErrorsAsStrings()).append('\n');
295      if (!reporter.errors.isEmpty())      sb.append("errors      : ").append(reporter.allErrorsAsStrings()).append('\n');
296      if (!reporter.warnings.isEmpty())    sb.append("warnings    : ").append(reporter.allWarningsAsStrings()).append('\n');
297      throw new JPPFException(sb.toString());
298    }
299  }
300}