/* * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.restconf.nb.rfc8040.streams.listeners; import java.io.IOException; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Optional; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.yangtools.concepts.Immutable; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; import org.w3c.dom.Document; abstract class EventFormatter implements Immutable { private static final XPathFactory XPF = XPathFactory.newInstance(); // FIXME: NETCONF-369: XPath operates without namespace context, therefore we need an namespace-unaware builder. // Once it is fixed we can use UntrustedXML instead. private static final @NonNull DocumentBuilderFactory DBF; static { final DocumentBuilderFactory f = DocumentBuilderFactory.newInstance(); f.setCoalescing(true); f.setExpandEntityReferences(false); f.setIgnoringElementContentWhitespace(true); f.setIgnoringComments(true); f.setXIncludeAware(false); try { f.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); f.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); f.setFeature("http://xml.org/sax/features/external-general-entities", false); f.setFeature("http://xml.org/sax/features/external-parameter-entities", false); } catch (final ParserConfigurationException e) { throw new ExceptionInInitializerError(e); } DBF = f; } private final XPathExpression filter; EventFormatter() { this.filter = null; } EventFormatter(final String xpathFilter) throws XPathExpressionException { final XPath xpath; synchronized (XPF) { xpath = XPF.newXPath(); } // FIXME: NETCONF-369: we need to bind the namespace context here and for that we need the SchemaContext filter = xpath.compile(xpathFilter); } final Optional eventData(final EffectiveModelContext schemaContext, final T input, final Instant now, final boolean leafNodesOnly, final boolean skipData) throws Exception { if (!filterMatches(schemaContext, input, now)) { return Optional.empty(); } return Optional.of(createText(schemaContext, input, now, leafNodesOnly, skipData)); } /** * Export the provided input into the provided document so we can verify whether a filter matches the content. * * @param doc the document to fill * @param schemaContext context to use for the export * @param input data to export * @throws IOException if any IOException occurs during export to the document */ abstract void fillDocument(Document doc, EffectiveModelContext schemaContext, T input) throws IOException; /** * Format the input data into string representation of the data provided. * * @param schemaContext context to use for the export * @param input input data * @param now time the event happened * @param leafNodesOnly option to include only leaves in the result * @param skipData option to skip data in the result, only paths would be included * @return String representation of the formatted data * @throws Exception if the underlying formatters fail to export the data to the requested format */ abstract String createText(EffectiveModelContext schemaContext, T input, Instant now, boolean leafNodesOnly, boolean skipData) throws Exception; private boolean filterMatches(final EffectiveModelContext schemaContext, final T input, final Instant now) throws IOException { if (filter == null) { return true; } final Document doc; try { doc = DBF.newDocumentBuilder().newDocument(); } catch (final ParserConfigurationException e) { throw new IOException("Failed to create a new document", e); } fillDocument(doc, schemaContext, input); final Boolean eval; try { eval = (Boolean) filter.evaluate(doc, XPathConstants.BOOLEAN); } catch (final XPathExpressionException e) { throw new IllegalStateException("Failed to evaluate expression " + filter, e); } return eval; } /** * Formats data specified by RFC3339. * * @param now time stamp * @return Data specified by RFC3339. */ static String toRFC3339(final Instant now) { return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(OffsetDateTime.ofInstant(now, ZoneId.systemDefault())); } }