// Copyright 2016 Google Inc. All Rights Reserved. // // Licensed 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 com.google.api.ads.common.lib.soap.testing; import com.google.common.base.Objects; import com.google.common.base.Strings; import com.google.common.collect.Maps; import org.custommonkey.xmlunit.Difference; import org.custommonkey.xmlunit.DifferenceConstants; import org.custommonkey.xmlunit.DifferenceListener; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import java.util.Map; import javax.xml.namespace.QName; /** * Implementation of XMLUnit {@link DifferenceListener} that performs additional checks when * namespace or attribute differences are found. The default implementation XMLUnit provides does * not check for logical equivalence between namespaces if the prefixes don't match or the namespace * is inherited from the parent node. */ public class CustomDifferenceListener implements DifferenceListener { @Override public void skippedComparison(Node control, Node test) { throw new IllegalArgumentException( String.format("Unable to compare nodes: %s, %s", control, test)); } @Override public int differenceFound(Difference difference) { switch (difference.getId()) { case DifferenceConstants.NAMESPACE_URI_ID: return namespaceDifferenceFound(difference); case DifferenceConstants.ELEMENT_NUM_ATTRIBUTES_ID: case DifferenceConstants.ATTR_NAME_NOT_FOUND_ID: return attributeDifferenceFound(difference); default: return DifferenceListener.RETURN_ACCEPT_DIFFERENCE; } } /** * Checks for logical equivalence of the qualified node names. */ private int namespaceDifferenceFound(Difference difference) { Node controlNode = difference.getControlNodeDetail().getNode(); Node testNode = difference.getTestNodeDetail().getNode(); String controlNs = getNamespaceURI(controlNode); String testNs = getNamespaceURI(testNode); if (Objects.equal(controlNs, testNs)) { if (Objects.equal(controlNode.getLocalName(), testNode.getLocalName())) { return DifferenceListener.RETURN_IGNORE_DIFFERENCE_NODES_SIMILAR; } } return DifferenceListener.RETURN_ACCEPT_DIFFERENCE; } /** * Ignores differences that are only due to missing xsi:type. */ private int attributeDifferenceFound(Difference difference) { NamedNodeMap controlAttributes = difference.getControlNodeDetail().getNode().getAttributes(); NamedNodeMap testAttributes = difference.getTestNodeDetail().getNode().getAttributes(); Map<QName, Node> controlAttributesMap = createAttributesMapExcludingXsiType(controlAttributes); Map<QName, Node> testAttributesMap = createAttributesMapExcludingXsiType(testAttributes); if (controlAttributesMap.size() != testAttributesMap.size()) { return DifferenceListener.RETURN_ACCEPT_DIFFERENCE; } return DifferenceListener.RETURN_IGNORE_DIFFERENCE_NODES_SIMILAR; } /** * Finds the namespace URI of the node, navigating up the node tree if necessary. * * @return the namespace URI of the node, which may be null. */ private String getNamespaceURI(Node node) { while (node != null && Strings.isNullOrEmpty(node.getNamespaceURI())) { node = node.getParentNode(); } return node == null ? null : node.getNamespaceURI(); } /** * Converts the XMLUnit {@link NamedNodeMap} into a map of {@link QName} to {@link Node}, * <em>excluding</em> {@code xsi:type} attributes. */ private Map<QName, Node> createAttributesMapExcludingXsiType(NamedNodeMap attributes) { Map<QName, Node> attributesMap = Maps.newHashMap(); for (int i = 0; i < attributes.getLength(); i++) { Node item = attributes.item(i); String itemNamespace = getNamespaceURI(item); String localName = item.getLocalName(); if (!("http://www.w3.org/2001/XMLSchema-instance".equals(itemNamespace) && "type".equals(localName))) { attributesMap.put(new QName(itemNamespace, item.getLocalName()), item); } } return attributesMap; } }