[java-metadata-aggregator] branch master updated: MDA-153 add generic validator framework
Ian Young
ian at iay.org.uk
Tue Dec 15 14:46:13 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.
The following commit(s) were added to refs/heads/master by this push:
new 8256dc2 MDA-153 add generic validator framework
8256dc2 is described below
commit 8256dc20a42f8cc0b21d48fd466bb78e02281f2b
Author: Ian Young <ian at iay.org.uk>
AuthorDate: Tue Dec 15 19:46:40 2015 +0000
MDA-153 add generic validator framework
Use chain of control and strategy patterns for validators. Pulled in
from the other branch of the ukf-mda project.
---
.../metadata/dom/AbstractDOMValidationStage.java | 7 +-
.../shibboleth/metadata/validate/Validator.java | 20 ++++-
.../validate/x509/AbstractX509Validator.java | 28 +++++++
.../validate/x509/X509RSAExponentValidator.java | 2 +-
.../validate/x509/X509RSAKeyLengthValidator.java | 2 +-
.../x509/X509RSAOpenSSLBlacklistValidator.java | 2 +-
.../dom/AbstractDOMValidationStageTest.java | 87 ++++++++++++++++++++++
.../net/shibboleth/metadata/dom/BaseDOMTest.java | 27 ++++++-
.../x509/X509RSAExponentValidatorTest.java | 3 +-
.../x509/X509RSAKeyLengthValidatorTest.java | 7 +-
.../x509/X509RSAOpenSSLBlacklistValidatorTest.java | 19 ++---
.../dom/AbstractDOMValidationStage-early.xml | 5 ++
12 files changed, 189 insertions(+), 20 deletions(-)
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractDOMValidationStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractDOMValidationStage.java
index 0bfd9ca..fec083d 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractDOMValidationStage.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/AbstractDOMValidationStage.java
@@ -73,8 +73,11 @@ public abstract class AbstractDOMValidationStage<V> extends AbstractDOMTraversal
*/
protected void applyValidators(@Nonnull final V obj, @Nonnull final TraversalContext context)
throws StageProcessingException {
- for (Validator<V> validator: validators) {
- validator.validate(obj, context.getItem(), getId());
+ for (final Validator<V> validator: validators) {
+ final Validator.Action action = validator.validate(obj, context.getItem(), getId());
+ if (action == Validator.Action.DONE) {
+ return;
+ }
}
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/Validator.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/Validator.java
index 21d61cb..65fe9a9 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/Validator.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/Validator.java
@@ -34,6 +34,23 @@ public interface Validator<V> extends DestructableComponent, IdentifiableCompone
InitializableComponent {
/**
+ * {@link Validator} instances are normally applied in sequence in a
+ * chain of responsibility pattern orchestrated by the caller.
+ *
+ * The sequence is regarded as completed when all validators have been called,
+ * or when the first returns a {@link #DONE} value. This can be used to
+ * short-circuit evaluation in the case where the problem detected would
+ * invalidate subsequent tests.
+ */
+ public enum Action {
+ /** Evaluation of other {@link Validator}s may proceed. */
+ CONTINUE,
+
+ /** Evaluation of other {@link Validator}s should not proceed. */
+ DONE
+ }
+
+ /**
* Apply the validator to the object in the given {@link Item} context.
*
* The validator influences future processing by adding item metadata to the {@link Item}.
@@ -42,9 +59,10 @@ public interface Validator<V> extends DestructableComponent, IdentifiableCompone
* @param item the {@link Item} context for the validation
* @param stageId the identifier for the stage that is requesting the validation, for
* inclusion in status metadata
+ * @return an indication of whether to process additional validators
* @throws StageProcessingException if an error occurs during validation
*/
- public void validate(@Nonnull V e, @Nonnull Item<?> item, @Nonnull String stageId)
+ public Action validate(@Nonnull V e, @Nonnull Item<?> item, @Nonnull String stageId)
throws StageProcessingException;
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/AbstractX509Validator.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/AbstractX509Validator.java
index 8017de6..8f2eb97 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/AbstractX509Validator.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/AbstractX509Validator.java
@@ -19,12 +19,40 @@ package net.shibboleth.metadata.validate.x509;
import java.security.cert.X509Certificate;
+import javax.annotation.Nonnull;
+
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.pipeline.StageProcessingException;
import net.shibboleth.metadata.validate.BaseValidator;
import net.shibboleth.metadata.validate.Validator;
/**
* Abstract class implementing validation on {@link X509Certificate} objects.
+ *
+ * X.509 certificate issues are almost always independent, so simplify the validator interface
+ * by delegation.
*/
public abstract class AbstractX509Validator extends BaseValidator implements Validator<X509Certificate> {
+ /**
+ * Apply the validator to the object in the given {@link Item} context.
+ *
+ * The validator influences future processing by adding item metadata to the {@link Item}.
+ *
+ * @param cert the certificate to be validated
+ * @param item the {@link Item} context for the validation
+ * @param stageId the identifier for the stage that is requesting the validation, for
+ * inclusion in status metadata
+ * @throws StageProcessingException if an error occurs during validation
+ */
+ protected abstract void doValidate(@Nonnull final X509Certificate cert, @Nonnull final Item<?> item,
+ @Nonnull final String stageId) throws StageProcessingException;
+
+ @Override
+ public Action validate(@Nonnull final X509Certificate cert, @Nonnull final Item<?> item,
+ @Nonnull final String stageId) throws StageProcessingException {
+ doValidate(cert, item, stageId);
+ return Action.CONTINUE;
+ }
+
}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/X509RSAExponentValidator.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/X509RSAExponentValidator.java
index d855645..17b11bf 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/X509RSAExponentValidator.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/X509RSAExponentValidator.java
@@ -94,7 +94,7 @@ public class X509RSAExponentValidator extends AbstractX509Validator {
}
@Override
- public void validate(@Nonnull final X509Certificate cert, @Nonnull final Item<?> item,
+ public void doValidate(@Nonnull final X509Certificate cert, @Nonnull final Item<?> item,
@Nonnull final String stageId) {
final PublicKey key = cert.getPublicKey();
if ("RSA".equals(key.getAlgorithm())) {
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/X509RSAKeyLengthValidator.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/X509RSAKeyLengthValidator.java
index 2a17b5b..a0d1284 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/X509RSAKeyLengthValidator.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/X509RSAKeyLengthValidator.java
@@ -91,7 +91,7 @@ public class X509RSAKeyLengthValidator extends AbstractX509Validator {
/** {@inheritDoc} */
@Override
- public void validate(@Nonnull final X509Certificate cert, @Nonnull final Item<?> item,
+ public void doValidate(@Nonnull final X509Certificate cert, @Nonnull final Item<?> item,
@Nonnull final String stageId) {
final PublicKey key = cert.getPublicKey();
if ("RSA".equals(key.getAlgorithm())) {
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/X509RSAOpenSSLBlacklistValidator.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/X509RSAOpenSSLBlacklistValidator.java
index 757ccd4..5398f24 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/X509RSAOpenSSLBlacklistValidator.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/validate/x509/X509RSAOpenSSLBlacklistValidator.java
@@ -165,7 +165,7 @@ public class X509RSAOpenSSLBlacklistValidator extends AbstractX509Validator {
/** {@inheritDoc} */
@Override
- public void validate(@Nonnull final X509Certificate cert, @Nonnull final Item<?> item,
+ public void doValidate(@Nonnull final X509Certificate cert, @Nonnull final Item<?> item,
@Nonnull final String stageId) throws StageProcessingException {
ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
final PublicKey key = cert.getPublicKey();
diff --git a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/AbstractDOMValidationStageTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/AbstractDOMValidationStageTest.java
new file mode 100644
index 0000000..08f12af
--- /dev/null
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/AbstractDOMValidationStageTest.java
@@ -0,0 +1,87 @@
+
+package net.shibboleth.metadata.dom;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.w3c.dom.Element;
+
+import net.shibboleth.metadata.ErrorStatus;
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.pipeline.StageProcessingException;
+import net.shibboleth.metadata.validate.BaseValidator;
+import net.shibboleth.metadata.validate.Validator;
+
+public class AbstractDOMValidationStageTest extends BaseDOMTest {
+
+ protected AbstractDOMValidationStageTest() {
+ super(AbstractDOMValidationStage.class);
+ }
+
+ private static class StringValidationStage extends AbstractDOMValidationStage<String> {
+
+ @Override
+ protected boolean applicable(Element element) {
+ return "checkedElement".equals(element.getLocalName());
+ }
+
+ @Override
+ protected void visit(Element element, TraversalContext context) throws StageProcessingException {
+ applyValidators(element.getTextContent(), context);
+ }
+
+ }
+
+ private static class FirstStringValidator extends BaseValidator implements Validator<String> {
+
+ @Override
+ public Action validate(String e, Item<?> item, String stageId)
+ throws StageProcessingException {
+ addError("element contains " + e, item, stageId);
+ return Action.DONE; // once per value checked
+ }
+
+ }
+
+ private static class UnexecutedStringValidator extends BaseValidator implements Validator<String> {
+
+ @Override
+ public Action validate(String e, Item<?> item, String stageId)
+ throws StageProcessingException {
+ Assert.fail("should not execute this");
+ return Action.CONTINUE;
+ }
+
+ }
+
+ @Test
+ public void testEarlyCompletion() throws Exception {
+ final FirstStringValidator v1 = new FirstStringValidator();
+ v1.setId("v1");
+ v1.initialize();
+ final UnexecutedStringValidator v2 = new UnexecutedStringValidator();
+ v2.setId("v2");
+ v2.initialize();
+ final List<Validator<String>> validators = new ArrayList<>();
+ validators.add(v1);
+ validators.add(v2);
+
+ final StringValidationStage s1 = new StringValidationStage();
+ s1.setId("s1");
+ s1.setValidators(validators);
+ s1.initialize();
+
+ final Item<Element> i1 = readDOMItem("early.xml");
+ final List<Item<Element>> items = new ArrayList<>();
+ items.add(i1);
+
+ s1.execute(items);
+ Assert.assertEquals(2, countErrors(i1));
+ final List<ErrorStatus> errors = i1.getItemMetadata().get(ErrorStatus.class);
+ Assert.assertEquals(errors.get(0).getStatusMessage(), "element contains one");
+ Assert.assertEquals(errors.get(1).getStatusMessage(), "element contains two");
+ }
+
+}
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 c99e122..bc9c951 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,13 @@
package net.shibboleth.metadata.dom;
import java.io.InputStream;
+import java.util.List;
import net.shibboleth.metadata.BaseTest;
+import net.shibboleth.metadata.ErrorStatus;
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.ItemMetadata;
+import net.shibboleth.utilities.java.support.collection.ClassToInstanceMultiMap;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.logic.Constraint;
import net.shibboleth.utilities.java.support.primitive.StringSupport;
@@ -74,7 +79,7 @@ public abstract class BaseDOMTest extends BaseTest {
*
* @return the document root of the data file, never null
*
- * @throws XMLParserException thrown if the file does not exists or there is a problem parsing it
+ * @throws XMLParserException thrown if the file does not exist or there is a problem parsing it
*/
public Element readXMLData(final String path) throws XMLParserException {
String trimmedPath = StringSupport.trimOrNull(path);
@@ -93,6 +98,20 @@ public abstract class BaseDOMTest extends BaseTest {
}
/**
+ * Reads in an XML file and returns it as a new {@link DOMElementItem}.
+ *
+ * @param path classpath path to the data file, never null
+ *
+ * @return an {@link Item} wrapping the document representing the data file, never null
+ *
+ * @throws XMLParserExceptionthrown if the file does not exist or there is a problem parsing it
+ */
+ public Item<Element> readDOMItem(final String path) throws XMLParserException {
+ final Element e = readXMLData(path);
+ return new DOMElementItem(e);
+ }
+
+ /**
* Checks whether two nodes are identical based on {@link Diff#identical()}.
*
* @param expected the expected node against which the actual node will be tested, never null
@@ -108,4 +127,10 @@ public abstract class BaseDOMTest extends BaseTest {
}
}
+ 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/validate/x509/X509RSAExponentValidatorTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/validate/x509/X509RSAExponentValidatorTest.java
index 83463e4..b76d45d 100644
--- a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/validate/x509/X509RSAExponentValidatorTest.java
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/validate/x509/X509RSAExponentValidatorTest.java
@@ -7,6 +7,7 @@ import net.shibboleth.metadata.Item;
import net.shibboleth.metadata.MockItem;
import net.shibboleth.metadata.validate.Validator;
+import org.testng.Assert;
import org.testng.annotations.Test;
public class X509RSAExponentValidatorTest extends BaseX509ValidatorTest {
@@ -21,7 +22,7 @@ public class X509RSAExponentValidatorTest extends BaseX509ValidatorTest {
final int expectedErrors, final int expectedWarnings) throws Exception {
final Item<String> item = new MockItem("foo");
final X509Certificate cert = getCertificate(certName);
- val.validate(cert, item, "stage");
+ Assert.assertEquals(val.validate(cert, item, "stage"), Validator.Action.CONTINUE);
errorsAndWarnings(item, expectedErrors, expectedWarnings);
}
diff --git a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/validate/x509/X509RSAKeyLengthValidatorTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/validate/x509/X509RSAKeyLengthValidatorTest.java
index 056ce14..7d9f701 100644
--- a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/validate/x509/X509RSAKeyLengthValidatorTest.java
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/validate/x509/X509RSAKeyLengthValidatorTest.java
@@ -7,6 +7,7 @@ import net.shibboleth.metadata.Item;
import net.shibboleth.metadata.MockItem;
import net.shibboleth.metadata.validate.Validator;
+import org.testng.Assert;
import org.testng.annotations.Test;
public class X509RSAKeyLengthValidatorTest extends BaseX509ValidatorTest {
@@ -21,7 +22,7 @@ public class X509RSAKeyLengthValidatorTest extends BaseX509ValidatorTest {
final Item<String> item = new MockItem("foo");
final Validator<X509Certificate> val = new X509RSAKeyLengthValidator();
final X509Certificate cert = getCertificate("2048.pem");
- val.validate(cert, item, "stage");
+ Assert.assertEquals(val.validate(cert, item, "stage"), Validator.Action.CONTINUE);
errorsAndWarnings(item, 0, 0);
}
@@ -30,7 +31,7 @@ public class X509RSAKeyLengthValidatorTest extends BaseX509ValidatorTest {
final Item<String> item = new MockItem("foo");
final Validator<X509Certificate> val = new X509RSAKeyLengthValidator();
final X509Certificate cert = getCertificate("1024.pem");
- val.validate(cert, item, "stage");
+ Assert.assertEquals(val.validate(cert, item, "stage"), Validator.Action.CONTINUE);
errorsAndWarnings(item, 1, 0);
}
@@ -41,7 +42,7 @@ public class X509RSAKeyLengthValidatorTest extends BaseX509ValidatorTest {
val.setErrorBoundary(1024);
val.setWarningBoundary(2048);
final X509Certificate cert = getCertificate("1024.pem");
- val.validate(cert, item, "stage");
+ Assert.assertEquals(val.validate(cert, item, "stage"), Validator.Action.CONTINUE);
errorsAndWarnings(item, 0, 1);
}
diff --git a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/validate/x509/X509RSAOpenSSLBlacklistValidatorTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/validate/x509/X509RSAOpenSSLBlacklistValidatorTest.java
index b0d198c..94bd213 100644
--- a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/validate/x509/X509RSAOpenSSLBlacklistValidatorTest.java
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/validate/x509/X509RSAOpenSSLBlacklistValidatorTest.java
@@ -9,6 +9,7 @@ import org.testng.annotations.Test;
import net.shibboleth.metadata.Item;
import net.shibboleth.metadata.MockItem;
+import net.shibboleth.metadata.validate.Validator;
public class X509RSAOpenSSLBlacklistValidatorTest extends BaseX509ValidatorTest {
@@ -26,7 +27,7 @@ public class X509RSAOpenSSLBlacklistValidatorTest extends BaseX509ValidatorTest
final Item<String> item = new MockItem("foo");
final X509Certificate cert = getCertificate("ok.pem");
- val.validate(cert, item, "stage");
+ Assert.assertEquals(val.validate(cert, item, "stage"), Validator.Action.CONTINUE);
errorsAndWarnings(item, 0, 0);
}
@@ -39,7 +40,7 @@ public class X509RSAOpenSSLBlacklistValidatorTest extends BaseX509ValidatorTest
final Item<String> item = new MockItem("foo");
final X509Certificate cert = getCertificate("1024.pem");
- val.validate(cert, item, "stage");
+ Assert.assertEquals(val.validate(cert, item, "stage"), Validator.Action.CONTINUE);
errorsAndWarnings(item, 1, 0);
}
@@ -52,7 +53,7 @@ public class X509RSAOpenSSLBlacklistValidatorTest extends BaseX509ValidatorTest
final Item<String> item = new MockItem("foo");
final X509Certificate cert = getCertificate("1024.pem");
- val.validate(cert, item, "stage");
+ Assert.assertEquals(val.validate(cert, item, "stage"), Validator.Action.CONTINUE);
errorsAndWarnings(item, 1, 0);
}
@@ -65,7 +66,7 @@ public class X509RSAOpenSSLBlacklistValidatorTest extends BaseX509ValidatorTest
final Item<String> item = new MockItem("foo");
final X509Certificate cert = getCertificate("1024.pem");
- val.validate(cert, item, "stage");
+ Assert.assertEquals(val.validate(cert, item, "stage"), Validator.Action.CONTINUE);
errorsAndWarnings(item, 0, 0);
}
@@ -79,7 +80,7 @@ public class X509RSAOpenSSLBlacklistValidatorTest extends BaseX509ValidatorTest
final Item<String> item = new MockItem("foo");
final X509Certificate cert = getCertificate("2048.pem");
val.validate(cert, item, "stage");
- errorsAndWarnings(item, 0, 0);
+ Assert.assertEquals(val.validate(cert, item, "stage"), Validator.Action.CONTINUE);
}
@Test
@@ -92,7 +93,7 @@ public class X509RSAOpenSSLBlacklistValidatorTest extends BaseX509ValidatorTest
final Item<String> item = new MockItem("foo");
final X509Certificate cert = getCertificate("2048.pem");
val.validate(cert, item, "stage");
- errorsAndWarnings(item, 1, 0);
+ Assert.assertEquals(val.validate(cert, item, "stage"), Validator.Action.CONTINUE);
}
@Test
@@ -105,7 +106,7 @@ public class X509RSAOpenSSLBlacklistValidatorTest extends BaseX509ValidatorTest
final Item<String> item = new MockItem("foo");
final X509Certificate cert = getCertificate("2048.pem");
val.validate(cert, item, "stage");
- errorsAndWarnings(item, 1, 0);
+ Assert.assertEquals(val.validate(cert, item, "stage"), Validator.Action.CONTINUE);
}
@Test
@@ -117,7 +118,7 @@ public class X509RSAOpenSSLBlacklistValidatorTest extends BaseX509ValidatorTest
final Item<String> item = new MockItem("foo");
final X509Certificate cert = getCertificate("2048.pem");
- val.validate(cert, item, "stage");
+ Assert.assertEquals(val.validate(cert, item, "stage"), Validator.Action.CONTINUE);
errorsAndWarnings(item, 0, 0);
}
@@ -137,7 +138,7 @@ public class X509RSAOpenSSLBlacklistValidatorTest extends BaseX509ValidatorTest
final Item<String> item = new MockItem("foo");
final X509Certificate cert = getCertificate("2048.pem");
- val.validate(cert, item, "stage");
+ Assert.assertEquals(val.validate(cert, item, "stage"), Validator.Action.CONTINUE);
errorsAndWarnings(item, 1, 0);
}
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/AbstractDOMValidationStage-early.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/AbstractDOMValidationStage-early.xml
new file mode 100644
index 0000000..289c3e1
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/AbstractDOMValidationStage-early.xml
@@ -0,0 +1,5 @@
+<root>
+ <checkedElement>one</checkedElement>
+ <checkedElement>two</checkedElement>
+ <uncheckedElement>three</uncheckedElement>
+</root>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list