/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package org.apache.geode.management.internal.configuration.utils; import org.apache.geode.cache.Cache; import org.apache.geode.cache.CacheFactory; import org.apache.geode.distributed.internal.ClusterConfigurationService; import org.apache.geode.internal.cache.extension.Extension; import org.apache.geode.internal.cache.xmlcache.CacheXml; import org.apache.geode.management.internal.configuration.domain.XmlEntity; import org.apache.geode.management.internal.configuration.utils.XmlUtils.XPathContext; import org.apache.geode.test.junit.categories.IntegrationTest; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPathExpressionException; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT; import static org.junit.Assert.assertEquals; /** * Unit tests for {@link XmlUtils#addNewNode(Document, XmlEntity)} and * {@link XmlUtils#deleteNode(Document, XmlEntity)}. Simulates the * {@link ClusterConfigurationService} method of extracting {@link XmlEntity} from the new config * and applying it to the current shared config. * * * @since GemFire 8.1 */ @Category(IntegrationTest.class) public class XmlUtilsAddNewNodeJUnitTest { private static final String TEST_PREFIX = "test"; private static final String TEST_NAMESPACE = "urn:java:org/apache/geode/management/internal/configuration/utils/XmlUtilsAddNewNodeJUnitTest"; private static final XPathContext xPathContext = new XPathContext(); private static Cache cache; private Document config; @BeforeClass public static void beforeClass() { cache = new CacheFactory().set(MCAST_PORT, "0").create(); xPathContext.addNamespace(CacheXml.PREFIX, CacheXml.GEODE_NAMESPACE); xPathContext.addNamespace(TEST_PREFIX, TEST_NAMESPACE); } @Before public void before() throws SAXException, ParserConfigurationException, IOException { config = XmlUtils.createDocumentFromReader(new InputStreamReader( this.getClass().getResourceAsStream("XmlUtilsAddNewNodeJUnitTest.xml"))); } @AfterClass public static void afterClass() { cache.close(); cache = null; } /** * Tests {@link XmlUtils#addNewNode(Document, XmlEntity)} with {@link CacheXml} element with a * <code>name</code> attribute, <code>region</code>. It should be added after other * <code>region</code> elements. * * @throws Exception * @throws XPathExpressionException * @since GemFire 8.1 */ @Test public void testAddNewNodeNewNamed() throws Exception { final String xPath = "/cache:cache/cache:region[@name='r3']"; NodeList nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(0, nodes.getLength()); final Document changes = XmlUtils.createDocumentFromReader(new InputStreamReader(this.getClass() .getResourceAsStream("XmlUtilsAddNewNodeJUnitTest.testAddNewNodeNewNamed.xml"))); nodes = XmlUtils.query(changes, xPath, xPathContext); assertEquals(1, nodes.getLength()); Element element = (Element) nodes.item(0); assertEquals(CacheXml.GEODE_NAMESPACE, element.getNamespaceURI()); final XmlEntity xmlEntity = XmlEntity.builder().withType("region").withAttribute("name", "r3") .withConfig(changes).build(); XmlUtils.addNewNode(config, xmlEntity); nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(1, nodes.getLength()); element = (Element) nodes.item(0); assertEquals(CacheXml.GEODE_NAMESPACE, element.getNamespaceURI()); final List<Node> childNodes = getElementNodes(config.getFirstChild().getChildNodes()); assertEquals("r2", childNodes.get(4).getAttributes().getNamedItem("name").getNodeValue()); assertEquals("r3", childNodes.get(5).getAttributes().getNamedItem("name").getNodeValue()); assertEquals("test:cache", childNodes.get(6).getNodeName()); } /** * Return just the nodes from a nodelist that are of type element. */ private List<Node> getElementNodes(NodeList nodes) { ArrayList<Node> result = new ArrayList<Node>(); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { result.add(node); } } return result; } /** * Tests {@link XmlUtils#addNewNode(Document, XmlEntity)} with {@link CacheXml} element that does * not have a name or id attribute, <code>jndi-bindings</code>. It should be added between * <code>pdx</code> and <code>region</code> elements. * * @throws Exception * @since GemFire 8.1 */ @Test public void testAddNewNodeNewUnnamed() throws Exception { final String xPath = "/cache:cache/cache:jndi-bindings"; NodeList nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(0, nodes.getLength()); final Document changes = XmlUtils.createDocumentFromReader(new InputStreamReader(this.getClass() .getResourceAsStream("XmlUtilsAddNewNodeJUnitTest.testAddNewNodeNewUnnamed.xml"))); nodes = XmlUtils.query(changes, xPath, xPathContext); assertEquals(1, nodes.getLength()); Element element = (Element) nodes.item(0); assertEquals(CacheXml.GEODE_NAMESPACE, element.getNamespaceURI()); final XmlEntity xmlEntity = XmlEntity.builder().withType("jndi-bindings").withConfig(changes).build(); XmlUtils.addNewNode(config, xmlEntity); nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(1, nodes.getLength()); element = (Element) nodes.item(0); assertEquals(CacheXml.GEODE_NAMESPACE, element.getNamespaceURI()); final List<Node> childElements = getElementNodes(config.getFirstChild().getChildNodes()); assertEquals("pdx", childElements.get(2).getNodeName()); assertEquals("jndi-bindings", childElements.get(3).getNodeName()); assertEquals("region", childElements.get(4).getNodeName()); } /** * Tests {@link XmlUtils#addNewNode(Document, XmlEntity)} with an {@link Extension} that does not * have a name or id attribute. It should be added to the end of the config xml. Attempts a name * collision with test:region, it should not collide with the similarly named cache:region * element. * * @throws Exception * @since GemFire 8.1 */ @Test public void testAddNewNodeNewUnnamedExtension() throws Exception { final String xPath = "/cache:cache/test:region"; NodeList nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(0, nodes.getLength()); final Document changes = XmlUtils.createDocumentFromReader(new InputStreamReader(this.getClass() .getResourceAsStream("XmlUtilsAddNewNodeJUnitTest.testAddNewNodeNewUnnamedExtension.xml"))); nodes = XmlUtils.query(changes, xPath, xPathContext); assertEquals(1, nodes.getLength()); Element element = (Element) nodes.item(0); assertEquals(TEST_NAMESPACE, element.getNamespaceURI()); assertEquals("test:region", element.getNodeName()); final XmlEntity xmlEntity = XmlEntity.builder().withType("region") .withNamespace(TEST_PREFIX, TEST_NAMESPACE).withConfig(changes).build(); XmlUtils.addNewNode(config, xmlEntity); nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(1, nodes.getLength()); element = (Element) nodes.item(0); assertEquals(TEST_NAMESPACE, element.getNamespaceURI()); assertEquals("test:region", element.getNodeName()); final List<Node> childElements = getElementNodes(config.getFirstChild().getChildNodes()); assertEquals("test:cache", childElements.get(5).getNodeName()); assertEquals("test:region", childElements.get(6).getNodeName()); assertEquals(7, childElements.size()); } /** * Tests {@link XmlUtils#addNewNode(Document, XmlEntity)} with {@link CacheXml} element with a * <code>name</code> attribute, <code>region</code>. It should replace existing * <code>region</code> element with same <code>name</code>. * * @throws Exception * @since GemFire 8.1 */ @Test public void testAddNewNodeReplaceNamed() throws Exception { final String xPath = "/cache:cache/cache:region[@name='r1']"; NodeList nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(1, nodes.getLength()); Element element = (Element) nodes.item(0); assertEquals(1, getElementNodes(element.getChildNodes()).size()); assertEquals(CacheXml.GEODE_NAMESPACE, element.getNamespaceURI()); final Document changes = XmlUtils.createDocumentFromReader(new InputStreamReader(this.getClass() .getResourceAsStream("XmlUtilsAddNewNodeJUnitTest.testAddNewNodeReplaceNamed.xml"))); nodes = XmlUtils.query(changes, xPath, xPathContext); assertEquals(1, nodes.getLength()); element = (Element) nodes.item(0); assertEquals(0, element.getChildNodes().getLength()); assertEquals(CacheXml.GEODE_NAMESPACE, element.getNamespaceURI()); final XmlEntity xmlEntity = XmlEntity.builder().withType("region").withAttribute("name", "r1") .withConfig(changes).build(); XmlUtils.addNewNode(config, xmlEntity); nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(1, nodes.getLength()); element = (Element) nodes.item(0); assertEquals(0, element.getChildNodes().getLength()); assertEquals(CacheXml.GEODE_NAMESPACE, element.getNamespaceURI()); } /** * Tests {@link XmlUtils#addNewNode(Document, XmlEntity)} with {@link CacheXml} element that does * not have a name or id attribute, <code>pdx</code>. It should replace <code>pdx</code> element. * * @throws Exception * @since GemFire 8.1 */ @Test public void testAddNewNodeReplaceUnnamed() throws Exception { final String xPath = "/cache:cache/cache:pdx"; NodeList nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(1, nodes.getLength()); Element element = (Element) nodes.item(0); assertEquals("foo", XmlUtils.getAttribute(element, "disk-store-name")); assertEquals(CacheXml.GEODE_NAMESPACE, element.getNamespaceURI()); final Document changes = XmlUtils.createDocumentFromReader(new InputStreamReader(this.getClass() .getResourceAsStream("XmlUtilsAddNewNodeJUnitTest.testAddNewNodeReplaceUnnamed.xml"))); nodes = XmlUtils.query(changes, xPath, xPathContext); assertEquals(1, nodes.getLength()); element = (Element) nodes.item(0); assertEquals("bar", XmlUtils.getAttribute(element, "disk-store-name")); assertEquals(CacheXml.GEODE_NAMESPACE, element.getNamespaceURI()); final XmlEntity xmlEntity = XmlEntity.builder().withType("pdx").withConfig(changes).build(); XmlUtils.addNewNode(config, xmlEntity); nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(1, nodes.getLength()); element = (Element) nodes.item(0); assertEquals("bar", XmlUtils.getAttribute(element, "disk-store-name")); assertEquals(CacheXml.GEODE_NAMESPACE, element.getNamespaceURI()); } /** * Tests {@link XmlUtils#addNewNode(Document, XmlEntity)} with an {@link Extension} that does not * have a name or id attribute, <code>test:cache</code>. It should replace the existing * <code>test:cache</code> element. * * @throws Exception * @since GemFire 8.1 */ @Test public void testAddNewNodeReplaceUnnamedExtension() throws Exception { final String xPath = "/cache:cache/test:cache"; NodeList nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(1, nodes.getLength()); Element element = (Element) nodes.item(0); assertEquals("1", XmlUtils.getAttribute(element, "value")); assertEquals(TEST_NAMESPACE, element.getNamespaceURI()); final org.w3c.dom.Document changes = XmlUtils.createDocumentFromReader(new InputStreamReader(this.getClass().getResourceAsStream( "XmlUtilsAddNewNodeJUnitTest.testAddNewNodeReplaceUnnamedExtension.xml"))); nodes = XmlUtils.query(changes, xPath, xPathContext); assertEquals(1, nodes.getLength()); element = (Element) nodes.item(0); assertEquals("2", XmlUtils.getAttribute(element, "value")); assertEquals(TEST_NAMESPACE, element.getNamespaceURI()); final XmlEntity xmlEntity = XmlEntity.builder().withType("cache") .withNamespace(TEST_PREFIX, TEST_NAMESPACE).withConfig(changes).build(); XmlUtils.addNewNode(config, xmlEntity); nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(1, nodes.getLength()); element = (Element) nodes.item(0); assertEquals("2", XmlUtils.getAttribute(element, "value")); assertEquals(TEST_NAMESPACE, element.getNamespaceURI()); } /** * Tests {@link XmlUtils#deleteNode(Document, XmlEntity)} with {@link CacheXml} element with a * <code>name</code> attribute, <code>region</code>. It should remove existing <code>region</code> * element with same <code>name</code>. * * @throws Exception * @since GemFire 8.1 */ @Test public void testDeleteNodeNamed() throws Exception { final String xPath = "/cache:cache/cache:region[@name='r1']"; NodeList nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(1, nodes.getLength()); Element element = (Element) nodes.item(0); assertEquals(1, getElementNodes(element.getChildNodes()).size()); assertEquals(CacheXml.GEODE_NAMESPACE, element.getNamespaceURI()); final Document changes = XmlUtils.createDocumentFromReader(new InputStreamReader(this.getClass() .getResourceAsStream("XmlUtilsAddNewNodeJUnitTest.testDeleteNodeNamed.xml"))); nodes = XmlUtils.query(changes, xPath, xPathContext); assertEquals(0, nodes.getLength()); final XmlEntity xmlEntity = XmlEntity.builder().withType("region").withAttribute("name", "r1") .withConfig(changes).build(); XmlUtils.deleteNode(config, xmlEntity); nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(0, nodes.getLength()); } /** * Tests {@link XmlUtils#addNewNode(Document, XmlEntity)} with {@link CacheXml} element that does * not have a name or id attribute, <code>pdx</code>. It should remove the existing * <code>pdx</code> element. * * @throws Exception * @since GemFire 8.1 */ @Test public void testDeleteNodeUnnamed() throws Exception { final String xPath = "/cache:cache/cache:pdx"; NodeList nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(1, nodes.getLength()); Element element = (Element) nodes.item(0); assertEquals("foo", XmlUtils.getAttribute(element, "disk-store-name")); assertEquals(CacheXml.GEODE_NAMESPACE, element.getNamespaceURI()); final Document changes = XmlUtils.createDocumentFromReader(new InputStreamReader(this.getClass() .getResourceAsStream("XmlUtilsAddNewNodeJUnitTest.testDeleteNodeUnnamed.xml"))); nodes = XmlUtils.query(changes, xPath, xPathContext); assertEquals(0, nodes.getLength()); final XmlEntity xmlEntity = XmlEntity.builder().withType("pdx").withConfig(changes).build(); XmlUtils.deleteNode(config, xmlEntity); nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(0, nodes.getLength()); } /** * Tests {@link XmlUtils#addNewNode(Document, XmlEntity)} with an {@link Extension} that does not * have a name or id attribute, <code>test:cache</code>. It should remove the existing * <code>test:cache</code> element. * * @throws Exception * @since GemFire 8.1 */ @Test public void testDeleteNodeUnnamedExtension() throws Exception { final String xPath = "/cache:cache/test:cache"; NodeList nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(1, nodes.getLength()); Element element = (Element) nodes.item(0); assertEquals("1", XmlUtils.getAttribute(element, "value")); assertEquals(TEST_NAMESPACE, element.getNamespaceURI()); final Document changes = XmlUtils.createDocumentFromReader(new InputStreamReader(this.getClass() .getResourceAsStream("XmlUtilsAddNewNodeJUnitTest.testDeleteNodeUnnamedExtension.xml"))); nodes = XmlUtils.query(changes, xPath, xPathContext); assertEquals(0, nodes.getLength()); final XmlEntity xmlEntity = XmlEntity.builder().withType("cache") .withNamespace(TEST_PREFIX, TEST_NAMESPACE).withConfig(changes).build(); XmlUtils.deleteNode(config, xmlEntity); nodes = XmlUtils.query(config, xPath, xPathContext); assertEquals(0, nodes.getLength()); } }