| /* |
| * 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.domain; |
| |
| import static javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI; |
| import static org.apache.geode.management.internal.configuration.utils.XmlConstants.W3C_XML_SCHEMA_INSTANCE_ATTRIBUTE_SCHEMA_LOCATION; |
| import static org.apache.geode.management.internal.configuration.utils.XmlUtils.getAttribute; |
| |
| import java.io.IOException; |
| import java.net.URL; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| |
| import javax.xml.XMLConstants; |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.xpath.XPathExpressionException; |
| |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.ext.EntityResolver2; |
| |
| import org.apache.geode.internal.cache.xmlcache.CacheXml; |
| import org.apache.geode.internal.cache.xmlcache.CacheXmlParser; |
| import org.apache.geode.management.internal.configuration.utils.XmlUtils; |
| import org.apache.geode.management.internal.configuration.utils.XmlUtils.XPathContext; |
| |
| /** |
| * Domain class to determine the order of an element Currently being used to store order information |
| * of child elements of "cache" |
| * |
| * |
| */ |
| // UnitTest CacheElementJUnitTest |
| public class CacheElement { |
| static final String XSD_PREFIX = "xsd"; |
| private static final String XSD_ALL_CHILDREN = "xsd:element"; |
| private static final String XSD_COMPLEX_TYPE_CHILDREN = |
| "xsd:group|xsd:all|xsd:choice|xsd:sequence"; |
| private static final String XSD_CHOICE_OR_SEQUENCE_CHILDREN = |
| "xsd:element|xsd:group|xsd:choice|xsd:sequence|xsd:any"; |
| |
| static final String CACHE_TYPE_EMBEDDED = |
| "/xsd:schema/xsd:element[@name='cache']/xsd:complexType"; |
| |
| private String name; |
| private int order; |
| private boolean multiple; |
| |
| public CacheElement(String name, int rank, boolean multiple) { |
| this.name = name; |
| this.order = rank; |
| this.multiple = multiple; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| public void setName(String name) { |
| this.name = name; |
| } |
| |
| public int getOrder() { |
| return order; |
| } |
| |
| public void setOrder(int order) { |
| this.order = order; |
| } |
| |
| public boolean isMultiple() { |
| return multiple; |
| } |
| |
| public void setMultiple(boolean multiple) { |
| this.multiple = multiple; |
| } |
| |
| /** |
| * Build <code>cache</code> element map for given <cod>doc</code>'s schemaLocation for |
| * {@link CacheXml#GEODE_NAMESPACE}. |
| * |
| * @param doc {@link Document} to parse schema for. |
| * @return Element map |
| * @since GemFire 8.1 |
| */ |
| public static LinkedHashMap<String, CacheElement> buildElementMap(final Document doc) |
| throws IOException, XPathExpressionException, SAXException, ParserConfigurationException { |
| Node cacheNode = doc.getFirstChild(); |
| if ("#comment".equals(cacheNode.getNodeName())) { |
| cacheNode = cacheNode.getNextSibling(); |
| } |
| final Map<String, String> schemaLocationMap = |
| XmlUtils.buildSchemaLocationMap(getAttribute(cacheNode, |
| W3C_XML_SCHEMA_INSTANCE_ATTRIBUTE_SCHEMA_LOCATION, W3C_XML_SCHEMA_INSTANCE_NS_URI)); |
| |
| final LinkedHashMap<String, CacheElement> elementMap = new LinkedHashMap<>(); |
| |
| buildElementMapCacheType(elementMap, |
| resolveSchema(schemaLocationMap, CacheXml.GEODE_NAMESPACE)); |
| |
| // if we are ever concerned with the order of extensions or children process them here. |
| |
| return elementMap; |
| } |
| |
| /** |
| * Resolve schema from <code>schemaLocationsNape</code> or <code>namespaceUri</code> for given |
| * <code>namespaceUri</code>. |
| * |
| * @param schemaLocationMap {@link Map} of namespaceUri to URLs. |
| * @param namespaceUri Namespace URI for schema. |
| * @return {@link InputSource} for schema if found. |
| * @throws IOException if unable to open {@link InputSource}. |
| * @since GemFire 8.1 |
| */ |
| private static InputSource resolveSchema(final Map<String, String> schemaLocationMap, |
| String namespaceUri) throws IOException { |
| final EntityResolver2 entityResolver = new CacheXmlParser(); |
| |
| InputSource inputSource = null; |
| |
| // Try loading schema from locations until we find one. |
| final String location = schemaLocationMap.get(namespaceUri); |
| |
| try { |
| inputSource = entityResolver.resolveEntity(null, location); |
| } catch (final SAXException e) { |
| // ignore |
| } |
| |
| if (null == inputSource) { |
| // Try getting it from the namespace, will throw if does not exist. |
| inputSource = new InputSource(new URL(namespaceUri).openStream()); |
| } |
| |
| return inputSource; |
| } |
| |
| /** |
| * Build element map adding to existing <code>elementMap</code>. |
| * |
| * @param elementMap to add elements to. |
| * @param inputSource to parse elements from. |
| * @since GemFire 8.1 |
| */ |
| private static void buildElementMapCacheType(final LinkedHashMap<String, CacheElement> elementMap, |
| final InputSource inputSource) |
| throws SAXException, IOException, ParserConfigurationException, XPathExpressionException { |
| final Document doc = XmlUtils.getDocumentBuilder().parse(inputSource); |
| |
| int rank = 0; |
| |
| final XPathContext xPathContext = |
| new XPathContext(XSD_PREFIX, XMLConstants.W3C_XML_SCHEMA_NS_URI); |
| final Node cacheType = XmlUtils.querySingleElement(doc, CACHE_TYPE_EMBEDDED, xPathContext); |
| |
| rank = buildElementMapXPath(elementMap, doc, cacheType, rank, XSD_COMPLEX_TYPE_CHILDREN, |
| xPathContext); |
| } |
| |
| /** |
| * Build element map for elements matching <code>xPath</code> relative to <code>parent</code> into |
| * <code>elementMap</code> . |
| * |
| * @param elementMap to add elements to |
| * @param schema {@link Document} for schema. |
| * @param parent {@link Element} to query XPath. |
| * @param rank current rank of elements. |
| * @param xPath XPath to query for elements. |
| * @param xPathContext XPath context for queries. |
| * @return final rank of elements. |
| * @since GemFire 8.1 |
| */ |
| private static int buildElementMapXPath(final LinkedHashMap<String, CacheElement> elementMap, |
| final Document schema, final Node parent, int rank, final String xPath, |
| final XPathContext xPathContext) throws XPathExpressionException { |
| final NodeList children = XmlUtils.query(parent, xPath, xPathContext); |
| for (int i = 0; i < children.getLength(); i++) { |
| final Element child = (Element) children.item(i); |
| switch (child.getNodeName()) { |
| case XSD_ALL_CHILDREN: |
| final String name = getAttribute(child, "name"); |
| elementMap.put(name, new CacheElement(name, rank++, isMultiple(child))); |
| break; |
| case "xsd:choice": |
| case "xsd:sequence": |
| rank = buildElementMapXPath(elementMap, schema, child, rank, |
| XSD_CHOICE_OR_SEQUENCE_CHILDREN, xPathContext); |
| break; |
| case "xsd:any": |
| // ignore extensions |
| break; |
| default: |
| throw new UnsupportedOperationException( |
| "Unsupported child type '" + child.getNodeName() + "'"); |
| } |
| } |
| |
| return rank; |
| } |
| |
| /** |
| * Tests if element allows multiple. |
| * |
| * @param element to test for multiple. |
| * @return true if multiple allowed, otherwise false. |
| * @since GemFire 8.1 |
| */ |
| private static boolean isMultiple(final Element element) { |
| String maxOccurs = getAttribute(element, "maxOccurs"); |
| return null != maxOccurs && !maxOccurs.equals("1"); |
| } |
| |
| } |