[java-metadata-aggregator] 01/02: MDA-56 - part 1: add Container framework

Ian Young ian at iay.org.uk
Wed Jun 28 11:55:03 EDT 2017


This is an automated email from the git hooks/post-receive script.

iay pushed a commit to branch master
in repository java-metadata-aggregator.

View the commit online:
http://git.shibboleth.net/view/?p=java-metadata-aggregator.git;a=commit;h=9e9f56838470c62cbf16159d9e93ad68eea5657d

commit 9e9f56838470c62cbf16159d9e93ad68eea5657d
Author: Ian Young <ian at iay.org.uk>
AuthorDate: Wed Jun 28 14:36:58 2017 +0100

    MDA-56 - part 1: add Container framework
---
 .../net/shibboleth/metadata/dom/Container.java     | 293 +++++++++++++++++++++
 .../net/shibboleth/metadata/dom/ElementMaker.java  |  53 ++++
 .../shibboleth/metadata/dom/ElementMatcher.java    |  55 ++++
 .../net/shibboleth/metadata/dom/BaseDOMTest.java   |  41 ++-
 .../net/shibboleth/metadata/dom/ContainerTest.java | 272 +++++++++++++++++++
 .../shibboleth/metadata/dom/ElementMakerTest.java  |  33 +++
 .../metadata/dom/ElementMatcherTest.java           |  32 +++
 .../net/shibboleth/metadata/dom/Container-add1.xml |   4 +
 .../shibboleth/metadata/dom/Container-addFirst.xml |   5 +
 .../shibboleth/metadata/dom/Container-addLast.xml  |   5 +
 .../net/shibboleth/metadata/dom/Container-find.xml |   9 +
 .../shibboleth/metadata/dom/Container-nested.xml   |   7 +
 12 files changed, 805 insertions(+), 4 deletions(-)

diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/Container.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/Container.java
new file mode 100644
index 0000000..25f9905
--- /dev/null
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/Container.java
@@ -0,0 +1,293 @@
+/*
+ * 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.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.xml.ElementSupport;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+
+/**
+ * A wrapper for a DOM {@link Element} allowing its use as a container for either
+ * a simple text value or for other {@link Element}s. In the latter case, white-space
+ * formatting is handled automatically for nested containers.
+ */
+public class Container {
+
+    /**
+     * When a container needs to add a new child {@link Element}, use a functor
+     * with this interface to create the appropriate context for the
+     * new child and to add it in place.
+     */
+    public interface ChildAddingStrategy {
+
+        /**
+         * Add the element in the appropriate position within the provided container.
+         * 
+         * @param parent container into which to add the new child
+         * @param child child {@link Element} to add
+         * @return a new child {@link Container} representing the child {@link Element}
+         */
+        @Nonnull Container addChild(@Nonnull final Container parent, @Nonnull final Element child);
+    }
+
+    /**
+     * Child adding strategy which adds the new child as the first child of the
+     * container.
+     */
+    @Nonnull public static final ChildAddingStrategy FIRST_CHILD = new ChildAddingStrategy() {
+
+        @Override
+        public @Nonnull Container addChild(@Nonnull final Container parent, @Nonnull final Element child) {
+            final Element parentElement = parent.unwrap();
+            final String indent = "\n" + parent.indentInner;
+            final Node indentNode = parentElement.getOwnerDocument().createTextNode(indent);
+            parent.prime();
+            parentElement.insertBefore(child, parentElement.getFirstChild());
+            parentElement.insertBefore(indentNode, parentElement.getFirstChild());
+            return new Container(child, parent);
+        }
+
+    };
+
+    /**
+     * Child adding strategy which adds the new child as the last child of the
+     * container.
+     */
+    @Nonnull public static final ChildAddingStrategy LAST_CHILD = new ChildAddingStrategy() {
+
+        @Override
+        public @Nonnull Container addChild(@Nonnull final Container parent, @Nonnull final Element child) {
+            final Element parentElement = parent.unwrap();
+            final Document document = parentElement.getOwnerDocument();
+            parent.prime();
+
+            // The end of the parent's node list will be the indentation for the closing tag,
+            // so push in some additional indentation for the child.
+            parentElement.appendChild(document.createTextNode(parent.indentStep));
+            parentElement.appendChild(child);
+
+            // Add a newline and indentation for the closing tag again
+            parentElement.appendChild(document.createTextNode("\n" + parent.indentOuter));
+
+            return new Container(child, parent);
+        }
+
+    };
+
+    /** Default indentation of four spaces. */
+    @Nonnull private static final String DEFAULT_INDENT = "    ";
+
+    /** The wrapped {@link Element}. */
+    @Nonnull private final Element element;
+
+    /** The parent {@link Container}, or <code>null</code>. */
+    @Nullable private final Container parentContainer;
+
+    /** The indentation applied to the opening and closing tags. */
+    @Nonnull private final String indentOuter;
+
+    /** The indentation applied to child containers. */
+    @Nonnull private final String indentInner;
+
+    /** The difference between outer and inner indentation. */
+    @Nonnull private final String indentStep;
+
+    /**
+     * Constructor.
+     * 
+     * @param elem {@link Element} on which to base the {@link Container}
+     * @param parent {@link Container} to link as the parent, or <code>null</code>
+     * @param outer indentation to be used for this container's start and end tags
+     * @param inner indentation to be used for child containers
+     * @param step difference between inner and outer indentation
+     */
+    private Container(@Nonnull final Element elem,
+            @Nullable final Container parent,
+            @Nonnull final String outer,
+            @Nonnull final String inner,
+            @Nonnull final String step) {
+        element = Constraint.isNotNull(elem, "element must not be null");
+        parentContainer = parent;
+        indentOuter = Constraint.isNotNull(outer, "outer indent must not be null");
+        indentInner = Constraint.isNotNull(inner, "inner indent must not be null");
+        indentStep = Constraint.isNotNull(step, "indent step must not be null");
+    }
+
+    /**
+     * Constructor.
+     * 
+     * This variant is used internally to construct {@link Container}s
+     * corresponding to child elements.
+     * 
+     * @param child child {@link Element} for which to construct a {@link Container}
+     * @param parent parentContainer {@link Container} to link this child to
+     */
+    private Container(@Nonnull final Element child, @Nonnull final Container parent) {
+        this(child, parent, parent.indentInner, parent.indentInner + parent.indentStep, parent.indentStep);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * This variant is used to start a {@link Container} tree
+     * from an existing root {@link Element}. It is the only
+     * public constructor.
+     * 
+     * @param root the existing {@link Element} to wrap.
+     */
+    public Container(@Nonnull final Element root) {
+        this(root, null, "", DEFAULT_INDENT, DEFAULT_INDENT);
+    }
+
+    /**
+     * Return the wrapped {@link Element}.
+     * 
+     * @return the wrapped {@link Element}
+     */
+    @Nonnull
+    public Element unwrap() {
+        return element;
+    }
+
+    /**
+     * Set the text content of the wrapped {@link Element}.
+     * 
+     * @param text the text content to set within the element
+     */
+    public void setText(@Nonnull final String text) {
+        Constraint.isNotNull(text, "text content must not be null");
+        element.setTextContent(text);
+    }
+
+    /**
+     * Make sure that the container is able to receive additional child
+     * containers.
+     * 
+     * If the container is empty, add in text content so that its opening
+     * and closing tags are on different lines but are indented in the same
+     * way.
+     * 
+     * The resulting container will have at least one child node, almost
+     * always a text node starting with "\n".
+     */
+    public void prime() {
+        if (!element.hasChildNodes()) {
+            setText("\n" + indentOuter);
+        }
+    }
+
+    /**
+     * Find an existing child matching the {@link Predicate}, if there is one.
+     * 
+     * @param matcher {@link Predicate} to match against existing children
+     * @return a child {@link Container} whose {@link Element} matches
+     *      the supplied {@link Predicate}.
+     */
+    @Nullable
+    public Container findChild(@Nonnull final Predicate<Element> matcher) {
+        for (final Element e : ElementSupport.getChildElements(element)) {
+            if (matcher.apply(e)) {
+                return new Container(e, this);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Find all existing children matching the {@link Predicate}.
+     * 
+     * @param matcher {@link Predicate} to match against existing children
+     * @return a {@link List} of all matching children
+     */
+    @Nonnull
+    public List<Container> findChildren(@Nonnull final Predicate<Element> matcher) {
+        final List<Container> list = new ArrayList<>();
+        for (final Element e : ElementSupport.getChildElements(element)) {
+            if (matcher.apply(e)) {
+                list.add(new Container(e, this));
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Add a child to the container.
+     * 
+     * @param child the new child element to add
+     * @param adder strategy class to place the new child inside the container
+     * @return a container wrapping the new child element
+     */
+    @Nonnull
+    public Container addChild(@Nonnull final Element child,
+            @Nonnull final ChildAddingStrategy adder) {
+        return adder.addChild(this, child);
+    }
+
+    /**
+     * Add a child to the container.
+     * 
+     * @param maker {@link Function} to create the new child element
+     * @param adder strategy class to place the new child inside the container
+     * @return a container wrapping the new child element
+     */
+    @Nonnull
+    public Container addChild(@Nonnull final Function<Container, Element> maker,
+            @Nonnull final ChildAddingStrategy adder) {
+        final Element child = maker.apply(this);
+        return addChild(child, adder);
+    }
+
+    /**
+     * Locate a child container matching the {@link Predicate}, creating one
+     * if necessary.
+     * 
+     * @param matcher {@link Predicate} to match against existing children
+     * @param maker a {@link Function} to create a new child {@link Element}
+     * @param adder a {@link ChildAddingStrategy} determining where to place the new child
+     * @return a child {@link Container}, possibly just created
+     */
+    @Nonnull
+    public Container locateChild(@Nonnull final Predicate<Element> matcher,
+            @Nonnull final Function<Container, Element> maker,
+            @Nonnull final ChildAddingStrategy adder) {
+        assert matcher != null;
+        assert maker != null;
+        assert adder != null;
+
+        // Return an existing child if one exists
+        final Container existing = findChild(matcher);
+        if (existing != null) {
+            return existing;
+        }
+
+        // Construct a new child and add it in the right place
+        return addChild(maker, adder);
+    }
+}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementMaker.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementMaker.java
new file mode 100644
index 0000000..233e9de
--- /dev/null
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementMaker.java
@@ -0,0 +1,53 @@
+/*
+ * 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 javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
+import javax.xml.namespace.QName;
+
+import net.shibboleth.utilities.java.support.xml.ElementSupport;
+
+import org.w3c.dom.Element;
+
+import com.google.common.base.Function;
+
+/**
+ * Basic maker class for {@link Element}s for use with the {@link Container} system.
+ */
+ at ThreadSafe
+public class ElementMaker implements Function<Container, Element> {
+
+    /** Qualified name for the {@link Element} to be created. */
+    @Nonnull private final QName name;
+
+    /**
+     * Constructor.
+     * 
+     * @param qname qualified name for the {@link Element} to be created
+     */
+    public ElementMaker(@Nonnull final QName qname) {
+        name = qname;
+    }
+
+    @Override
+    public Element apply(@Nonnull final Container input) {
+        return ElementSupport.constructElement(input.unwrap().getOwnerDocument(), name);
+    }
+
+}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementMatcher.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementMatcher.java
new file mode 100644
index 0000000..aeab7cc
--- /dev/null
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/ElementMatcher.java
@@ -0,0 +1,55 @@
+/*
+ * 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 javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+import javax.xml.namespace.QName;
+
+import net.shibboleth.utilities.java.support.xml.ElementSupport;
+
+import org.w3c.dom.Element;
+
+import com.google.common.base.Predicate;
+
+/**
+ * Basic matcher class for {@link Element}s for use with the {@link Container} system.
+ */
+ at ThreadSafe
+public class ElementMatcher implements Predicate<Element> {
+
+    /** Element {@link QName} to match. */
+    @Nonnull private final QName qname;
+
+    /**
+     * Constructor.
+     * 
+     * @param qnameToMatch qualified name ({@link QName}) to match
+     */
+    public ElementMatcher(@Nullable final QName qnameToMatch) {
+        assert qnameToMatch != null;
+        qname = qnameToMatch;
+    }
+
+    @Override
+    public boolean apply(@Nonnull final Element input) {
+        return ElementSupport.isElementNamed(input, qname);
+    }
+
+}
diff --git a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/BaseDOMTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/BaseDOMTest.java
index f37bfeb..3904a1e 100644
--- a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/BaseDOMTest.java
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/BaseDOMTest.java
@@ -18,8 +18,11 @@
 package net.shibboleth.metadata.dom;
 
 import java.io.InputStream;
+import java.io.StringReader;
 import java.util.List;
 
+import javax.annotation.Nonnull;
+
 import net.shibboleth.metadata.BaseTest;
 import net.shibboleth.metadata.ErrorStatus;
 import net.shibboleth.metadata.Item;
@@ -30,10 +33,12 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
 import net.shibboleth.utilities.java.support.xml.BasicParserPool;
 import net.shibboleth.utilities.java.support.xml.ParserPool;
+import net.shibboleth.utilities.java.support.xml.SerializeSupport;
 import net.shibboleth.utilities.java.support.xml.XMLParserException;
 
 import org.custommonkey.xmlunit.Diff;
 import org.custommonkey.xmlunit.XMLUnit;
+import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
@@ -48,7 +53,7 @@ public abstract class BaseDOMTest extends BaseTest {
     protected BaseDOMTest(final Class<?> clazz) {
         super(clazz);
     }
-    
+
     /**
      * Setup test class. Creates and initializes the parser pool.
      * 
@@ -70,7 +75,7 @@ public abstract class BaseDOMTest extends BaseTest {
     public ParserPool getParserPool() {
         return parserPool;
     }
-    
+
     /**
      * Reads in an XML file, parses it, and returns the document element. If the given path is relative (i.e., does not
      * start with a '/') it is assumed to be relative to the class, or to /data if the class has not been set.
@@ -126,11 +131,39 @@ public abstract class BaseDOMTest extends BaseTest {
             org.testng.Assert.fail(diff.toString());
         }
     }
-    
+
+    /**
+     * Checks whether two nodes are equal based on {@link Node#isEqualNode(Node)}. Both nodes are serialized, re-parsed,
+     * and then compared for equality. This forces any changes made to the document that haven't yet been represented in
+     * the DOM (e.g., declaration of used namespaces) to be flushed to the DOM.
+     * 
+     * @param expected the expected node against which the actual node will be tested, never null
+     * @param actual the actual node tested against the expected node, never null
+     * 
+     * @throws XMLParserException thrown if there is a problem serializing and re-parsing the nodes
+     */
+    public void assertXMLEqual(@Nonnull final Node expected, @Nonnull final Node actual) throws XMLParserException {
+        Constraint.isNotNull(actual, "Actual Node may not be null");
+        final String serializedActual = SerializeSupport.nodeToString(actual);
+        Element deserializedActual = parserPool.parse(new StringReader(serializedActual)).getDocumentElement();
+
+        Constraint.isNotNull(expected, "Expected Node may not be null");
+        final String serializedExpected = SerializeSupport.nodeToString(expected);
+        Element deserializedExpected = parserPool.parse(new StringReader(serializedExpected)).getDocumentElement();
+
+        final boolean ok = deserializedExpected.isEqualNode(deserializedActual);
+        if (!ok) {
+            System.out.println("Expected:\n" + serializedExpected);
+            System.out.println("Actual:\n" + serializedActual);
+        }
+
+        Assert.assertTrue(ok, "Actual Node does not equal expected Node");
+    }
+
     protected int countErrors(final Item<Element> item) {
         final ClassToInstanceMultiMap<ItemMetadata> metadata = item.getItemMetadata();
         final List<ErrorStatus> errors = metadata.get(ErrorStatus.class);
         return errors.size();
     }
-    
+
 }
diff --git a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/ContainerTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/ContainerTest.java
new file mode 100644
index 0000000..5fbb0b4
--- /dev/null
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/ContainerTest.java
@@ -0,0 +1,272 @@
+
+package net.shibboleth.metadata.dom;
+
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+
+public class ContainerTest extends BaseDOMTest {
+
+    private final Document doc;
+
+    protected ContainerTest() throws Exception {
+        super(Container.class);
+        setUp();
+        doc = getParserPool().newDocument();
+    }
+
+    @Test
+    public void prime() {
+        // simple top-level element with no indentation
+        final Element e1 = doc.createElementNS("ns", "el");
+        final Container c1 = new Container(e1);
+        c1.prime();
+        Assert.assertEquals(e1.getTextContent(), "\n");
+
+        // prime it again
+        c1.prime();
+        Assert.assertEquals(e1.getTextContent(), "\n");
+
+        // an indented container
+        final Element e2 = doc.createElementNS("ns", "el2");
+        e1.insertBefore(e2, e1.getFirstChild());
+        final Container c2 = c1.findChild(Predicates.<Element>alwaysTrue());
+        Assert.assertNotNull(c2);
+        c2.prime();
+        Assert.assertEquals(e2.getTextContent(), "\n    ");
+
+        // prime it again
+        c2.prime();
+        Assert.assertEquals(e2.getTextContent(), "\n    ");
+    }
+
+    @Test
+    public void addChildElementFirst() throws Exception {
+        final Element e1 = doc.createElementNS("ns", "root");
+        final Element e2 = doc.createElementNS("ns", "child");
+        e2.setTextContent("child value");
+        final Container c1 = new Container(e1);
+        c1.addChild(e2, Container.FIRST_CHILD);
+
+        final Element ok = readXMLData("add1.xml");
+        assertXMLEqual(ok, e1);
+
+        final Element e3 = doc.createElementNS("ns", "child2");
+        e3.setTextContent("child 2 value");
+        c1.addChild(e3, Container.FIRST_CHILD);
+        final Element ok2 = readXMLData("addFirst.xml");
+        assertXMLEqual(ok2, e1);
+    }
+
+    @Test
+    public void addChildElementLast() throws Exception {
+        final Element e1 = doc.createElementNS("ns", "root");
+        final Element e2 = doc.createElementNS("ns", "child");
+        e2.setTextContent("child value");
+        final Container c1 = new Container(e1);
+        c1.addChild(e2, Container.LAST_CHILD);
+
+        final Element ok = readXMLData("add1.xml");
+        assertXMLEqual(ok, e1);
+
+        final Element e3 = doc.createElementNS("ns", "child2");
+        e3.setTextContent("child 2 value");
+        c1.addChild(e3, Container.LAST_CHILD);
+        final Element ok2 = readXMLData("addLast.xml");
+        assertXMLEqual(ok2, e1);
+    }
+
+    @Test
+    public void addChildElementNested() throws Exception {
+        final Element root = doc.createElementNS("ns", "root");
+        final Element mid = doc.createElementNS("ns", "mid");
+        final Element leaf1 = doc.createElementNS("ns", "leaf");
+        leaf1.setTextContent("leaf 1");
+        final Element leaf2 = doc.createElementNS("ns", "leaf");
+        leaf2.setTextContent("leaf 2");
+        final Container rootContainer = new Container(root);
+        final Container midContainer = rootContainer.addChild(mid, Container.FIRST_CHILD);
+        midContainer.addChild(leaf1, Container.LAST_CHILD);
+        midContainer.addChild(leaf2, Container.LAST_CHILD);
+
+        final Element ok = readXMLData("nested.xml");
+        assertXMLEqual(ok, root);
+    }
+
+    @Test
+    public void addChildFunctionFirst() throws Exception {
+        final Element e1 = doc.createElementNS("ns", "root");
+        final Container c1 = new Container(e1);
+
+        c1.addChild(new Function<Container, Element>(){
+
+            @Override
+            public Element apply(Container input) {
+                final Element e2 = doc.createElementNS("ns", "child");
+                e2.setTextContent("child value");
+                return e2;
+            }
+            
+        }, Container.FIRST_CHILD);
+
+        final Element ok = readXMLData("add1.xml");
+        assertXMLEqual(ok, e1);
+
+        c1.addChild(new Function<Container, Element>(){
+
+            @Override
+            public Element apply(Container input) {
+                final Element e3 = doc.createElementNS("ns", "child2");
+                e3.setTextContent("child 2 value");
+                return e3;
+            }
+            
+        }, Container.FIRST_CHILD);
+
+        final Element ok2 = readXMLData("addFirst.xml");
+        assertXMLEqual(ok2, e1);
+    }
+
+    @Test
+    public void addChildFunctionLast() throws Exception {
+        final Element e1 = doc.createElementNS("ns", "root");
+        final Container c1 = new Container(e1);
+
+        c1.addChild(new Function<Container, Element>(){
+
+            @Override
+            public Element apply(Container input) {
+                final Element e2 = doc.createElementNS("ns", "child");
+                e2.setTextContent("child value");
+                return e2;
+            }
+            
+        }, Container.LAST_CHILD);
+
+        final Element ok = readXMLData("add1.xml");
+        assertXMLEqual(ok, e1);
+
+        c1.addChild(new Function<Container, Element>(){
+
+            @Override
+            public Element apply(Container input) {
+                final Element e3 = doc.createElementNS("ns", "child2");
+                e3.setTextContent("child 2 value");
+                return e3;
+            }
+            
+        }, Container.LAST_CHILD);
+
+        final Element ok2 = readXMLData("addLast.xml");
+        assertXMLEqual(ok2, e1);
+    }
+
+    @Test
+    public void unwrap() {
+        final Element leaf1 = doc.createElementNS("ns", "leaf");
+        leaf1.setTextContent("leaf 1");
+        final Container c = new Container(leaf1);
+        Assert.assertEquals(c.unwrap().getTextContent(), "leaf 1");
+    }
+
+    @Test
+    public void setText() {
+        final Element e = doc.createElementNS("ns", "root");
+        final Container c = new Container(e);
+        c.setText("some text");
+        Assert.assertEquals(e.getTextContent(), "some text");
+    }
+
+    @Test
+    public void findChild() throws Exception {
+        final Container root = new Container(readXMLData("find.xml"));
+        final Container child = root.findChild(new Predicate<Element>(){
+
+            @Override
+            public boolean apply(Element input) {
+                return "findme".equals(input.getLocalName());
+            }
+            
+        });
+        Assert.assertNotNull(child);
+        Assert.assertEquals(child.unwrap().getTextContent(), "find me 1");
+    }
+
+    @Test
+    public void findChildren() throws Exception {
+        final Container root = new Container(readXMLData("find.xml"));
+        final List<Container> children = root.findChildren(new Predicate<Element>(){
+
+            @Override
+            public boolean apply(Element input) {
+                return "findme".equals(input.getLocalName());
+            }
+            
+        });
+        Assert.assertNotNull(children);
+        Assert.assertEquals(children.size(), 3);
+        Assert.assertEquals(children.get(0).unwrap().getTextContent(), "find me 1");
+        Assert.assertEquals(children.get(1).unwrap().getTextContent(), "find me 2");
+        Assert.assertEquals(children.get(2).unwrap().getTextContent(), "find me 3");
+    }
+
+    @Test
+    public void locateChild() throws Exception {
+        final Element e1 = doc.createElementNS("ns", "root");
+        final Container c1 = new Container(e1);
+
+        c1.locateChild(new Predicate<Element>(){
+
+            @Override
+            public boolean apply(Element input) {
+                return "child".equals(input.getLocalName());
+            }
+            
+            
+        }, new Function<Container, Element>(){
+
+            @Override
+            public Element apply(Container input) {
+                final Element e2 = doc.createElementNS("ns", "child");
+                e2.setTextContent("child value");
+                return e2;
+            }
+            
+        }, Container.LAST_CHILD);
+
+        final Element ok = readXMLData("add1.xml");
+        assertXMLEqual(ok, e1);
+
+        // same again should NOT change the result for locate
+
+        c1.locateChild(new Predicate<Element>(){
+
+            @Override
+            public boolean apply(Element input) {
+                return "child".equals(input.getLocalName());
+            }
+            
+            
+        }, new Function<Container, Element>(){
+
+            @Override
+            public Element apply(Container input) {
+                final Element e2 = doc.createElementNS("ns", "child");
+                e2.setTextContent("child value");
+                return e2;
+            }
+            
+        }, Container.LAST_CHILD);
+
+        final Element ok2 = readXMLData("add1.xml");
+        assertXMLEqual(ok2, e1);
+
+    }
+}
diff --git a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/ElementMakerTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/ElementMakerTest.java
new file mode 100644
index 0000000..8b0f210
--- /dev/null
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/ElementMakerTest.java
@@ -0,0 +1,33 @@
+
+package net.shibboleth.metadata.dom;
+
+import javax.xml.namespace.QName;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import com.google.common.base.Function;
+
+public class ElementMakerTest extends BaseDOMTest {
+
+    private final Document doc;
+
+    protected ElementMakerTest() throws Exception {
+        super(ElementMaker.class);
+        setUp();
+        doc = getParserPool().newDocument();
+    }
+
+    @Test
+    public void apply() {
+        final Function<Container, Element> maker = new ElementMaker(new QName("ns", "local"));
+        final Element root = doc.createElementNS("ns", "root");
+        final Container rootContainer = new Container(root);
+        final Element newElement = maker.apply(rootContainer);
+        Assert.assertNotNull(newElement);
+        Assert.assertEquals(newElement.getLocalName(), "local");
+        Assert.assertEquals(newElement.getNamespaceURI(), "ns");
+    }
+}
diff --git a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/ElementMatcherTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/ElementMatcherTest.java
new file mode 100644
index 0000000..371c789
--- /dev/null
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/ElementMatcherTest.java
@@ -0,0 +1,32 @@
+
+package net.shibboleth.metadata.dom;
+
+import javax.xml.namespace.QName;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import com.google.common.base.Predicate;
+
+public class ElementMatcherTest extends BaseDOMTest {
+
+    private final Document doc;
+
+    protected ElementMatcherTest() throws Exception {
+        super(ElementMatcher.class);
+        setUp();
+        doc = getParserPool().newDocument();
+    }
+
+    @Test
+    public void matcher() throws Exception {
+        final Predicate<Element> matcher = new ElementMatcher(new QName("ns", "xxx"));
+        Assert.assertTrue(matcher.apply(doc.createElementNS("ns", "xxx")));
+        Assert.assertFalse(matcher.apply(doc.createElementNS("ns", "yyy")));
+        Assert.assertFalse(matcher.apply(doc.createElementNS("ns2", "xxx")));
+        Assert.assertFalse(matcher.apply(doc.createElementNS("ns2", "yyy")));
+    }
+
+}
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/Container-add1.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/Container-add1.xml
new file mode 100644
index 0000000..317ded9
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/Container-add1.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root xmlns="ns">
+    <child>child value</child>
+</root>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/Container-addFirst.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/Container-addFirst.xml
new file mode 100644
index 0000000..301b160
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/Container-addFirst.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root xmlns="ns">
+    <child2>child 2 value</child2>
+    <child>child value</child>
+</root>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/Container-addLast.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/Container-addLast.xml
new file mode 100644
index 0000000..0f552d6
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/Container-addLast.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root xmlns="ns">
+    <child>child value</child>
+    <child2>child 2 value</child2>
+</root>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/Container-find.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/Container-find.xml
new file mode 100644
index 0000000..859f20c
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/Container-find.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root xmlns="ns">
+    <child>child value</child>
+    <findme>find me 1</findme>
+    <another>ignorable child</another>
+    <findme>find me 2</findme>
+    <another>ignore me as well</another>
+    <findme>find me 3</findme>
+</root>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/Container-nested.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/Container-nested.xml
new file mode 100644
index 0000000..2d9c17c
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/Container-nested.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root xmlns="ns">
+    <mid>
+        <leaf>leaf 1</leaf>
+        <leaf>leaf 2</leaf>
+    </mid>
+</root>

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list