001/*
002 * JPPF.
003 * Copyright (C) 2005-2015 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");
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    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    InputStream is = FileUtils.getFileInputStream(docPath);
065    if (is == null) return null;
066    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    InputSource is = new InputSource(reader);
079    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 Node findFirstElement(final Document doc) {
089    NodeList list = doc.getChildNodes();
090    for (int i=0; i<list.getLength(); i++) {
091      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    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    NodeList list = node.getChildNodes();
112    for (int i=0; i<list.getLength(); i++) {
113      Node childNode = list.item(i);
114      if (childNode.getNodeType() == Node.ELEMENT_NODE) {
115        String name = childNode.getNodeName();
116        /*if ("Script".equals(name)) desc.script = getTextNodeValue(childNode);
117        else*/ if (RULE_NAMES.contains(name)) desc.children.add(generateTree(childNode));
118        else if ("Property".equals(name) || "Value".equals(name) || "Subnet".equals(name)) desc.operands.add(getTextNodeValue(childNode));
119        else if ("Arg".equals(name)) desc.arguments.add(getTextNodeValue(childNode));
120      }
121    }
122    return desc;
123  }
124
125  /**
126   * Get the value of a node's text subelement.
127   * @param node the node to generate whose child is a text node.
128   * @return the text as a string.
129   */
130  private String getTextNodeValue(final Node node) {
131    NodeList children = node.getChildNodes();
132    for (int j=0; j<children.getLength(); j++) {
133      Node childNode = children.item(j);
134      int type = childNode.getNodeType();
135      if ((type == Node.TEXT_NODE) || (type == Node.CDATA_SECTION_NODE)) return childNode.getNodeValue();
136    }
137    return null;
138  }
139
140  /**
141   * Get the value of the attriobute of a node.
142   * @param node the node from which to get the attribute.
143   * @param name the name of the attribute to get.
144   * @param def the default value to use if the attribute is not defined.
145   * @return the attribute value as a string.
146   */
147  private String getAttributeValue(final Node node, final String name, final String def) {
148    NamedNodeMap attrMap = node.getAttributes();
149    Node attrNode = attrMap.getNamedItem(name);
150    return attrNode == null ? def : attrNode.getNodeValue();
151  }
152
153  /**
154   * Test of the parser.
155   * @param args not used.
156   */
157  public static void main(final String...args) {
158    try {
159      String docPath = "ExecutionPolicy.xml";
160      JPPFErrorReporter reporter = new JPPFErrorReporter(docPath);
161      String schemaPath = "org/jppf/schemas/ExecutionPolicy.xsd";
162      SchemaValidator validator = new SchemaValidator(reporter);
163      if (!validator.validate(docPath, schemaPath)) {
164        String s = "the document " + docPath;
165        System.out.println(s + " has errors.");
166        System.out.println("fatal errors: " + reporter.allFatalErrorsAsStrings());
167        System.out.println("errors      : " + reporter.allErrorsAsStrings());
168        System.out.println("warnings    : " + reporter.allWarningsAsStrings());
169        return;
170      }
171      PolicyParser parser = new PolicyParser();
172      PolicyDescriptor desc = parser.parse(docPath);
173      ExecutionPolicy policy = new PolicyBuilder().buildPolicy(desc.children.get(0));
174      System.out.println("Successfully build policy object:\n" + policy);
175    } catch(Exception e) {
176      e.printStackTrace();
177    }
178  }
179
180  /**
181   * Parse an XML document representing an execution policy.
182   * @param policyContent an XML string containing the policy.
183   * @return an <code>ExecutionPolicy</code> instance.
184   * @throws Exception if an error occurs during the validation or parsing.
185   */
186  public static ExecutionPolicy parsePolicy(final String policyContent) throws Exception {
187    return parsePolicy(new StringReader(policyContent));
188  }
189
190  /**
191   * Parse an XML document representing an execution policy from a file path.
192   * @param docPath path to the XML document file.
193   * @return an <code>ExecutionPolicy</code> instance.
194   * @throws Exception if an error occurs during the validation or parsing.
195   */
196  public static ExecutionPolicy parsePolicyFile(final String docPath) throws Exception {
197    return parsePolicy(FileUtils.getFileReader(docPath));
198  }
199
200  /**
201   * Parse an XML document representing an execution policy from a file path.
202   * @param policyFile abstract path of the XML document file.
203   * @return an <code>ExecutionPolicy</code> instance.
204   * @throws Exception if an error occurs during the validation or parsing.
205   */
206  public static ExecutionPolicy parsePolicy(final File policyFile) throws Exception {
207    return parsePolicy(new BufferedReader(new FileReader(policyFile)));
208  }
209
210  /**
211   * Parse an XML document representing an execution policy from an input stream.
212   * @param stream an input stream from which the XML representation of the policy is read.
213   * @return an <code>ExecutionPolicy</code> instance.
214   * @throws Exception if an error occurs during the validation or parsing.
215   */
216  public static ExecutionPolicy parsePolicy(final InputStream stream) throws Exception {
217    return parsePolicy(new InputStreamReader(stream));
218  }
219
220  /**
221   * Parse an XML document representing an execution policy from a reader.
222   * @param reader reader from which the XML representation of the policy is read.
223   * @return an <code>ExecutionPolicy</code> instance.
224   * @throws Exception if an error occurs during the validation or parsing.
225   */
226  public static ExecutionPolicy parsePolicy(final Reader reader) throws Exception {
227    PolicyDescriptor desc = new PolicyParser().parse(reader);
228    return new PolicyBuilder().buildPolicy(desc.children.get(0));
229  }
230
231  /**
232   * Validate an XML document representing an execution policy against the
233   * <a href="http://www.jppf.org/schemas/ExecutionPolicy.xsd">JPPF Execution Policy schema</a>.
234   * @param policyContent the XML content of the policy document.
235   * @throws JPPFException if there is a validation error. The details of the errors are included in the exception message.
236   * @throws Exception if an error occurs during the validation.
237   */
238  public static void validatePolicy(final String policyContent) throws JPPFException, Exception {
239    try (Reader reader = new StringReader(policyContent)) {
240      validatePolicy(reader);
241    }
242  }
243
244  /**
245   * Validate an XML document representing an execution policy against the
246   * <a href="http://www.jppf.org/schemas/ExecutionPolicy.xsd">JPPF Execution Policy schema</a>.
247   * @param docPath path to the XML document file.
248   * @throws JPPFException if there is a validation error. The details of the errors are included in the exception message.
249   * @throws Exception if an error occurs during the validation.
250   */
251  public static void validatePolicyFile(final String docPath) throws JPPFException, Exception {
252    validatePolicy(FileUtils.getFileReader(docPath));
253  }
254
255  /**
256   * Validate an XML document representing an execution policy against the
257   * <a href="http://www.jppf.org/schemas/ExecutionPolicy.xsd">JPPF Execution Policy schema</a>.
258   * @param docPath abstract path of the XML document file.
259   * @throws JPPFException if there is a validation error. The details of the errors are included in the exception message.
260   * @throws Exception if an error occurs during the validation.
261   */
262  public static void validatePolicy(final File docPath) throws JPPFException, Exception {
263    validatePolicy(new BufferedReader(new FileReader(docPath)));
264  }
265
266  /**
267   * Validate an XML document representing an execution policy against the
268   * <a href="http://www.jppf.org/schemas/ExecutionPolicy.xsd">JPPF Execution Policy schema</a>.
269   * @param stream an input stream from which the XML representation of the policy is read.
270   * @throws JPPFException if there is a validation error. The details of the errors are included in the exception message.
271   * @throws Exception if an error occurs during the validation or parsing.
272   */
273  public static void validatePolicy(final InputStream stream) throws JPPFException, Exception {
274    validatePolicy(new InputStreamReader(stream));
275  }
276
277  /**
278   * Validate an XML document representing an execution policy against the
279   * <a href="http://www.jppf.org/schemas/ExecutionPolicy.xsd">JPPF Execution Policy schema</a>.
280   * @param reader reader from which the XML representation of the policy is read.
281   * @throws JPPFException if there is a validation error. The details of the errors are included in the exception message.
282   * @throws Exception if an error occurs during the validation or parsing.
283   */
284  public static void validatePolicy(final Reader reader) throws JPPFException, Exception {
285    JPPFErrorReporter reporter = new JPPFErrorReporter("XML validator");
286    String schemaPath = "org/jppf/schemas/ExecutionPolicy.xsd";
287    SchemaValidator validator = new SchemaValidator(reporter);
288    if (!validator.validate(reader, FileUtils.getFileReader(schemaPath))) {
289      StringBuilder sb = new StringBuilder();
290      sb.append("The XML document has errors:\n");
291      if (!reporter.fatalErrors.isEmpty()) sb.append("fatal errors: ").append(reporter.allFatalErrorsAsStrings()).append('\n');
292      if (!reporter.errors.isEmpty())      sb.append("errors      : ").append(reporter.allErrorsAsStrings()).append('\n');
293      if (!reporter.warnings.isEmpty())    sb.append("warnings    : ").append(reporter.allWarningsAsStrings()).append('\n');
294      throw new JPPFException(sb.toString());
295    }
296  }
297}