[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