[java-metadata-aggregator] 01/03: MDA-150 add stage to whitelist/blacklist multiple namespaces
Ian Young
ian at iay.org.uk
Tue Dec 1 05:08:11 EST 2015
This is an automated email from the git hooks/post-receive script.
iay pushed a commit to branch master
in repository java-metadata-aggregator.
commit 0cb4899ec46928744f6073d02b44ed19b9bd7445
Author: Ian Young <ian at iay.org.uk>
AuthorDate: Tue Dec 1 10:51:02 2015 +0100
MDA-150 add stage to whitelist/blacklist multiple namespaces
Import NamespacesStrippingStage and AbstractNamesapacesStrippingStage
from ukf-mda project.
---
.../dom/AbstractNamespacesStrippingStage.java | 189 +++++++++++++++++++++
.../metadata/dom/NamespacesStrippingStage.java | 116 +++++++++++++
.../metadata/dom/NamespacesStrippingStageTest.java | 97 +++++++++++
.../metadata/dom/NamespacesStrippingStage-1-in.xml | 15 ++
.../dom/NamespacesStrippingStage-1-out.xml | 13 ++
.../metadata/dom/NamespacesStrippingStage-2-bl.xml | 18 ++
.../metadata/dom/NamespacesStrippingStage-2-in.xml | 24 +++
.../metadata/dom/NamespacesStrippingStage-2-wl.xml | 16 ++
8 files changed, 488 insertions(+)
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractNamespacesStrippingStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractNamespacesStrippingStage.java
new file mode 100644
index 0000000..0b85276
--- /dev/null
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractNamespacesStrippingStage.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.metadata.dom;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
+import javax.xml.XMLConstants;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import net.shibboleth.metadata.ErrorStatus;
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.ItemMetadata;
+import net.shibboleth.metadata.pipeline.BaseStage;
+import net.shibboleth.metadata.pipeline.StageProcessingException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.collection.ClassToInstanceMultiMap;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * An abstract stage which removes all evidence of given XML namespaces from each metadata item.
+ *
+ * Determining the affected namespaces is delegated to subclasses. Elements, attributes and
+ * namespace prefix definitions associated with a given namespace will be removed
+ * or retained as determined by the {@link #removingNamespace} function.
+ *
+ * Attributes without an explicit namespace prefix will never be removed.
+ */
+ at ThreadSafe
+public abstract class AbstractNamespacesStrippingStage extends BaseStage<Element> {
+
+ /** Class logger. */
+ private final Logger log = LoggerFactory.getLogger(AbstractNamespacesStrippingStage.class);
+
+ /**
+ * Determine whether a particular namespace should be stripped.
+ *
+ * @param namespace potentially stripped namespace
+ * @return <code>true</code> if this namespace should be stripped
+ */
+ protected abstract boolean removingNamespace(final String namespace);
+
+ /**
+ * Processes the given {@link Item}.
+ *
+ * @param item {@link Item} to process.
+ */
+ private void processItem(@Nonnull final Item<Element> item) {
+ final Element element = Constraint.isNotNull(item, "Item can not be null").unwrap();
+
+ /*
+ * We can't, by definition, remove the document element from a {@link DOMElementItem},
+ * so fail quickly if the document element is in the target namespace.
+ */
+ if (removingNamespace(element.getNamespaceURI())) {
+ ClassToInstanceMultiMap<ItemMetadata> metadata = item.getItemMetadata();
+ metadata.put(new ErrorStatus(getId(), "can't strip namespace from document element"));
+ return;
+ }
+
+ processElement(element, 0);
+ }
+
+ /**
+ * Process the attributes on an element.
+ *
+ * Assumes that the element itself does not reside in the target namespace,
+ * and that all child elements have already been processed.
+ *
+ * @param element the {@link Element} to process
+ */
+ private void processAttributes(@Nonnull final Element element) {
+ Constraint.isNotNull(element, "Element can not be null");
+
+ /*
+ * Process the attributes on this element. Because the NamedNodeMap
+ * associated with an element is "live", we need to collect the attributes
+ * we want to remove and do that at the end.
+ */
+ final NamedNodeMap attributes = element.getAttributes();
+ final List<Attr> removeTarget = new ArrayList<>();
+ final List<Attr> removePrefix = new ArrayList<>();
+ for (int aIndex = 0; aIndex < attributes.getLength(); aIndex++) {
+ final Attr attribute = (Attr) attributes.item(aIndex);
+ final String attrNamespace = attribute.getNamespaceURI();
+ final String attrLocalName = attribute.getLocalName();
+ log.trace("checking attribute {{}}:{}", attrNamespace, attrLocalName);
+
+ // Handle namespace prefix definitions first
+ if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(attrNamespace)) {
+ // namespace prefix definition
+ if (removingNamespace(attribute.getTextContent())) {
+ // remove prefix definition
+ log.trace(" prefix {} definition; will remove", attrLocalName);
+ removeTarget.add(attribute);
+ }
+
+ } else if (attrNamespace != null && removingNamespace(attrNamespace)) {
+ // remove attribute in target namespace
+ // never remove attributes without an explicit namespace prefix
+ log.trace(" in target namespace; will remove");
+ removePrefix.add(attribute);
+ }
+ }
+
+ /*
+ * Actually remove attributes we don't want any more.
+ *
+ * Remove the prefix declarations last, just in case that matters.
+ */
+ for (final Attr a: removeTarget) {
+ element.removeAttributeNode(a);
+ }
+ for (final Attr a: removePrefix) {
+ element.removeAttributeNode(a);
+ }
+ }
+
+ /**
+ * Process an individual DOM element.
+ *
+ * @param element element to process
+ * @param depth processing depth, starting with 0 for the document element.
+ */
+ private void processElement(@Nonnull final Element element, final int depth) {
+ Constraint.isNotNull(element, "Element can not be null");
+ log.trace("{}: element {}", depth, element.getLocalName());
+
+ /*
+ * If this element is in the target namespace, remove it from the DOM entirely and we're done.
+ */
+ if (removingNamespace(element.getNamespaceURI())) {
+ log.trace("{}: removing element entirely", depth);
+ element.getParentNode().removeChild(element);
+ return;
+ }
+
+ /*
+ * Recursively process the DOM below this element.
+ */
+ final NodeList children = element.getChildNodes();
+ for (int eIndex = 0; eIndex < children.getLength(); eIndex++) {
+ final Node child = children.item(eIndex);
+ if (child instanceof Element) {
+ processElement((Element) child, depth+1);
+ }
+ }
+
+ /*
+ * Process the attribute collection on this element,
+ * including attributes acting as namespace prefix definitions.
+ */
+ processAttributes(element);
+ }
+
+ @Override
+ protected void doExecute(@Nonnull @NonnullElements final Collection<Item<Element>> items)
+ throws StageProcessingException {
+ for (Item<Element> item : items) {
+ processItem(item);
+ }
+ }
+
+}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/NamespacesStrippingStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/NamespacesStrippingStage.java
new file mode 100644
index 0000000..4606ebe
--- /dev/null
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/NamespacesStrippingStage.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.metadata.dom;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
+
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+
+/**
+ * A stage which removes all evidence of a given collection of XML namespaces from each metadata item.
+ *
+ * The stage can operate either to blacklist (the default) or whitelist the collection of namespaces.
+ *
+ * Elements, attributes and namespace prefix definitions associated with a given namespace will be removed
+ * or retained depending on the {@link whitelisting} property.
+ *
+ * Attributes without an explicit namespace prefix will never be removed by this stage.
+ */
+ at ThreadSafe
+public class NamespacesStrippingStage extends AbstractNamespacesStrippingStage {
+
+ /**
+ * XML namespaces to whitelist or blacklist.
+ */
+ private final Set<String> namespaces = new HashSet<>();
+
+ /**
+ * Whether we are whitelisting or blacklisting (default: blacklisting).
+ */
+ private boolean whitelisting;
+
+ /**
+ * Gets the collection of namespaces being blacklisted or whitelisted.
+ *
+ * @return collection of namespaces being removed
+ */
+ @Nonnull
+ public Collection<String> getNamespaces() {
+ final Set<String> result = new HashSet<>();
+ result.addAll(namespaces);
+ return result;
+ }
+
+ /**
+ * Sets the collection of namespaces to blacklist or whitelist.
+ *
+ * @param nss collection of namespaces
+ */
+ public void setNamespaces(@Nonnull final Collection<String> nss) {
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ namespaces.clear();
+ namespaces.addAll(nss);
+ }
+
+ /**
+ * Indicate whether the stage is whitelisting namespaces or blacklisting (the default).
+ *
+ * @return <code>true</code> for whitelisting, <code>false</code> for blacklisting (the default)
+ */
+ public boolean isWhitelisting() {
+ return whitelisting;
+ }
+
+ /**
+ * Set whether the stage is whitelisting namespaces.
+ *
+ * @param wl <code>true</code> for whitelisting, <code>false</code> for blacklisting
+ */
+ public void setWhitelisting(final boolean wl) {
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ whitelisting = wl;
+ }
+
+ /**
+ * Determine whether a particular namespace should be stripped.
+ *
+ * @param namespace potentially stripped namespace
+ * @return <code>true</code> if this namespace should be stripped
+ */
+ @Override
+ protected boolean removingNamespace(final String namespace) {
+ return whitelisting ^ namespaces.contains(namespace);
+ }
+
+ @Override
+ protected void doDestroy() {
+ namespaces.clear();
+
+ super.doDestroy();
+ }
+
+}
diff --git a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/NamespacesStrippingStageTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/NamespacesStrippingStageTest.java
new file mode 100644
index 0000000..9d87e79
--- /dev/null
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/NamespacesStrippingStageTest.java
@@ -0,0 +1,97 @@
+package net.shibboleth.metadata.dom;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.testng.annotations.Test;
+import org.w3c.dom.Element;
+
+import net.shibboleth.metadata.Item;
+
+public class NamespacesStrippingStageTest extends BaseDOMTest {
+
+ /** Constructor sets class under test. */
+ public NamespacesStrippingStageTest() {
+ super(NamespacesStrippingStage.class);
+ }
+
+ /**
+ * Test the simple case that is already handled by NamespaceStrippingStage,
+ * of blacklisting a single namespace.
+ *
+ * @throws Exception if something goes wrong
+ */
+ @Test
+ public void simple() throws Exception {
+ final Element doc = readXMLData("1-in.xml");
+ final Item<Element> item = new DOMElementItem(doc);
+ final List<Item<Element>> items = new ArrayList<>();
+ items.add(item);
+
+ final NamespacesStrippingStage stage = new NamespacesStrippingStage();
+ stage.setId("stripTest");
+ final List<String> namespaces = new ArrayList<>();
+ namespaces.add("urn:namespace:beta");
+ stage.setNamespaces(namespaces);
+ stage.initialize();
+
+ stage.execute(items);
+
+ final Element out = readXMLData("1-out.xml");
+ assertXMLIdentical(out, item.unwrap());
+ }
+
+ /**
+ * Test blacklisting.
+ *
+ * @throws Exception if something goes wrong
+ */
+ @Test
+ public void blacklist() throws Exception {
+ final Element doc = readXMLData("2-in.xml");
+ final Item<Element> item = new DOMElementItem(doc);
+ final List<Item<Element>> items = new ArrayList<>();
+ items.add(item);
+
+ final NamespacesStrippingStage stage = new NamespacesStrippingStage();
+ stage.setId("stripTest");
+ final List<String> namespaces = new ArrayList<>();
+ namespaces.add("urn:namespace:bravo");
+ namespaces.add("urn:namespace:charlie");
+ stage.setNamespaces(namespaces);
+ stage.initialize();
+
+ stage.execute(items);
+
+ final Element out = readXMLData("2-bl.xml");
+ assertXMLIdentical(out, item.unwrap());
+ }
+
+ /**
+ * Test whitelisting.
+ *
+ * @throws Exception if something goes wrong
+ */
+ @Test
+ public void whitelist() throws Exception {
+ final Element doc = readXMLData("2-in.xml");
+ final Item<Element> item = new DOMElementItem(doc);
+ final List<Item<Element>> items = new ArrayList<>();
+ items.add(item);
+
+ final NamespacesStrippingStage stage = new NamespacesStrippingStage();
+ stage.setId("stripTest");
+ final List<String> namespaces = new ArrayList<>();
+ namespaces.add("urn:namespace:alfa"); // root element
+ namespaces.add("urn:namespace:bravo");
+ namespaces.add("urn:namespace:charlie");
+ stage.setNamespaces(namespaces);
+ stage.setWhitelisting(true);
+ stage.initialize();
+
+ stage.execute(items);
+
+ final Element out = readXMLData("2-wl.xml");
+ assertXMLIdentical(out, item.unwrap());
+ }
+}
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/NamespacesStrippingStage-1-in.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/NamespacesStrippingStage-1-in.xml
new file mode 100644
index 0000000..3cf57af
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/NamespacesStrippingStage-1-in.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<rootElement xmlns:alpha="urn:namespace:alpha" xmlns:betabeta="urn:namespace:beta">
+ <nested beta:attr="betaAttr" keepThis="1" alpha:keep="2" xmlns:beta="urn:namespace:beta">
+ <keepMe keep="keep" beta:bb="bb" alpha:a="a" beta:b="b">
+ <nested beta:removeThis="3">
+ <alsoKept/>
+ </nested>
+ </keepMe>
+ </nested>
+ <alpha:keepMe>
+ <betaxxx:removeMe xmlns:betaxxx="urn:namespace:beta">
+ <alsoRemoved/>
+ </betaxxx:removeMe>
+ </alpha:keepMe>
+</rootElement>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/NamespacesStrippingStage-1-out.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/NamespacesStrippingStage-1-out.xml
new file mode 100644
index 0000000..eb9f7d8
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/NamespacesStrippingStage-1-out.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<rootElement xmlns:alpha="urn:namespace:alpha">
+ <nested keepThis="1" alpha:keep="2">
+ <keepMe keep="keep" alpha:a="a">
+ <nested>
+ <alsoKept/>
+ </nested>
+ </keepMe>
+ </nested>
+ <alpha:keepMe>
+
+ </alpha:keepMe>
+</rootElement>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/NamespacesStrippingStage-2-bl.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/NamespacesStrippingStage-2-bl.xml
new file mode 100644
index 0000000..c48d723
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/NamespacesStrippingStage-2-bl.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<a:rootElement xmlns:a="urn:namespace:alfa" xmlns:d="urn:namespace:delta">
+ <nested keepThis="1" a:keep="2">
+ <keepMe keep="keep" a:a="a">
+ <nested>
+ <alsoKept/>
+ </nested>
+ </keepMe>
+ </nested>
+ <a:keepMe keep="17">
+
+ </a:keepMe>
+
+
+ <d:delta>
+ This is an element in the delta namespace
+ </d:delta>
+</a:rootElement>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/NamespacesStrippingStage-2-in.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/NamespacesStrippingStage-2-in.xml
new file mode 100644
index 0000000..c7b223f
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/NamespacesStrippingStage-2-in.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<a:rootElement xmlns:a="urn:namespace:alfa" xmlns:b="urn:namespace:bravo" xmlns:c="urn:namespace:charlie" xmlns:d="urn:namespace:delta">
+ <nested b:attr="bAttr" keepThis="1" a:keep="2">
+ <keepMe keep="keep" b:bb="bb" a:a="a" b:b="b">
+ <nested b:removeThis="3">
+ <alsoKept/>
+ </nested>
+ </keepMe>
+ </nested>
+ <a:keepMe keep="17">
+ <b:removeMe xmlns:betaxxx="urn:namespace:bravo">
+ <alsoRemoved/>
+ </b:removeMe>
+ </a:keepMe>
+ <b:bravo>
+ This is an element in the bravo namespace
+ </b:bravo>
+ <c:charlie>
+ This is an element in the charlie namespace
+ </c:charlie>
+ <d:delta>
+ This is an element in the delta namespace
+ </d:delta>
+</a:rootElement>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/NamespacesStrippingStage-2-wl.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/NamespacesStrippingStage-2-wl.xml
new file mode 100644
index 0000000..1b912f8
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/NamespacesStrippingStage-2-wl.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<a:rootElement xmlns:a="urn:namespace:alfa" xmlns:b="urn:namespace:bravo" xmlns:c="urn:namespace:charlie">
+
+ <a:keepMe keep="17">
+ <b:removeMe xmlns:betaxxx="urn:namespace:bravo">
+
+ </b:removeMe>
+ </a:keepMe>
+ <b:bravo>
+ This is an element in the bravo namespace
+ </b:bravo>
+ <c:charlie>
+ This is an element in the charlie namespace
+ </c:charlie>
+
+</a:rootElement>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list