From d069d1df07fc100076e4558ed2b9001a2f97cbe3 Mon Sep 17 00:00:00 2001 From: Ian Young Date: Sat, 25 Mar 2017 22:33:35 +0000 Subject: [PATCH] Add InCommonEntityOrderingStrategy Initial implementation and basic test. See incommon/inc-meta#55. --- .checkstyle | 4 +- .gitignore | 1 + .project | 4 +- pom.xml | 30 +-- .../saml/InCommonEntityOrderingStrategy.java | 179 ++++++++++++++++++ .../InCommonEntityOrderingStrategyTest.java | 78 ++++++++ ...InCommonEntityOrderingStrategy-trivial.xml | 4 + 7 files changed, 285 insertions(+), 15 deletions(-) create mode 100644 src/main/java/uk/org/iay/incommon/dom/saml/InCommonEntityOrderingStrategy.java create mode 100644 src/test/java/uk/org/iay/incommon/dom/saml/InCommonEntityOrderingStrategyTest.java create mode 100644 src/test/resources/uk/org/iay/incommon/dom/saml/InCommonEntityOrderingStrategy-trivial.xml diff --git a/.checkstyle b/.checkstyle index d3f95a2..8474622 100644 --- a/.checkstyle +++ b/.checkstyle @@ -1,10 +1,10 @@ - + - + diff --git a/.gitignore b/.gitignore index b83d222..289d9e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target/ +/test-output/ diff --git a/.project b/.project index a45af57..a746277 100644 --- a/.project +++ b/.project @@ -16,12 +16,12 @@ - org.eclipse.m2e.core.maven2Builder + net.sf.eclipsecs.core.CheckstyleBuilder - net.sf.eclipsecs.core.CheckstyleBuilder + org.eclipse.m2e.core.maven2Builder diff --git a/pom.xml b/pom.xml index 9db8085..4ee204b 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,7 @@ 0.9.2 + 0.9.4 7.2.0 7.6 @@ -41,6 +42,20 @@ false + + ukf-release + https://apps.iay.org.uk/nexus/content/repositories/ukf + + false + + + + ukf-snapshot + https://apps.iay.org.uk/nexus/content/repositories/ukf-snapshots + + false + + @@ -51,10 +66,6 @@ com.google.code.findbugs jsr305 - - com.google.guava - guava - net.shibboleth.metadata aggregator-pipeline @@ -73,13 +84,10 @@ - ${spring.groupId} - spring-context - test - - - ${spring.groupId} - spring-test + uk.org.ukfederation + ukf-mda + ${ukf-mda.version} + test-jar test diff --git a/src/main/java/uk/org/iay/incommon/dom/saml/InCommonEntityOrderingStrategy.java b/src/main/java/uk/org/iay/incommon/dom/saml/InCommonEntityOrderingStrategy.java new file mode 100644 index 0000000..6b30521 --- /dev/null +++ b/src/main/java/uk/org/iay/incommon/dom/saml/InCommonEntityOrderingStrategy.java @@ -0,0 +1,179 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.org.iay.incommon.dom.saml; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import javax.annotation.Nonnull; +import javax.annotation.concurrent.ThreadSafe; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Element; + +import net.shibboleth.metadata.Item; +import net.shibboleth.metadata.ItemId; +import net.shibboleth.metadata.dom.saml.EntitiesDescriptorAssemblerStage.ItemOrderingStrategy; +import net.shibboleth.metadata.dom.saml.mdrpi.RegistrationAuthority; +import net.shibboleth.metadata.pipeline.StageProcessingException; +import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements; + +/** + * Implements an ordering strategy for InCommon federation aggregates. + * + * The input list contains entities registered by InCommon in a specific + * order which must not be disturbed. They are followed by entities registered + * elsewhere; these are to be grouped by registrar and then within registrar + * ordered by entityID. + * + * The registrar and entityID values used for ordering are required to be + * present in the item's item metadata. + */ +@ThreadSafe +public class InCommonEntityOrderingStrategy implements ItemOrderingStrategy { + + /** Class logger. */ + private final Logger log = LoggerFactory.getLogger(InCommonEntityOrderingStrategy.class); + + /** The registrar whose entities should always appear first, in the provided order. */ + private final String distinguishedRegistrar; + + /** + * Helper class which wraps an {@link Element} {@link Item} but extracts any + * associated {@link RegistrationAuthority} and {@link ItemId} for simpler comparisons. + */ + private static class OrderableItem implements Comparable { + + /** The wrapped {@link Element} {@link Item}. */ + private final Item item; + + /** The registrar for this entity. */ + private final String registrar; + + /** The entityID for this entity. */ + private final String entityID; + + /** + * Constructor. + * + * @param domItem the {@link Element} {@link Item} to wrap + * @param reg the registrar for this entity + * @param entity the entityID for this entity + */ + public OrderableItem(@Nonnull final Item domItem, + @Nonnull final String reg, @Nonnull final String entity) { + item = domItem; + registrar = reg; + entityID = entity; + } + + @Override + public int compareTo(@Nonnull final OrderableItem o) { + // compare registrar values + final int c = registrar.compareTo(o.registrar); + // done if unequal + if (c != 0) { + return c; + } + // compare entityID values if registrars equal + return entityID.compareTo(o.entityID); + } + + /** + * Unwrap the wrapped {@link Element} {@link Item}. + * + * @return the wrapped {@link Element} {@link Item}. + */ + public Item unwrap() { + return item; + } + } + + /** + * Constructor. + * + * @param registrar distinguished registrar, whose entities sort first + */ + public InCommonEntityOrderingStrategy(@Nonnull final String registrar) { + distinguishedRegistrar = registrar; + } + + @Override + public List> order(@Nonnull @NonnullElements final Collection> items) { + + // Collect the results here + final List> results = new ArrayList<>(items.size()); + + /* + * Construct an orderable list wrapping the original items. + * + * Any belonging to the distinguished registrar are instead put straight in the + * results list. + */ + final List orderableList = new ArrayList<>(items.size()); + try { + for (Item item : items) { + final List registrars = item.getItemMetadata().get(RegistrationAuthority.class); + final String registrar; + if (registrars.size() == 0) { + throw new StageProcessingException("entity does not have RegistrationAuthority item metadata"); + } else { + registrar = registrars.get(0).getRegistrationAuthority(); + if (distinguishedRegistrar.equals(registrar)) { + results.add(item); + continue; + } + } + + final List itemids = item.getItemMetadata().get(ItemId.class); + final String entityID; + if (itemids.size() == 0) { + throw new StageProcessingException("entity does not have ItemId item metadata"); + } else { + entityID = itemids.get(0).getId(); + } + + orderableList.add(new OrderableItem(item, registrar, entityID)); + } + } catch (StageProcessingException e) { + /* + * If the ordering operation fails because we can't create an OrderableItem + * for each original item, it's probably because we are missing some item + * metadata on one or more items. The signature for the order method does + * not allow this to be reported as of MDA 0.9.x. + * + * This will change in MDA 0.10.0 (see IDP-175) but for now all we + * can really do is log an error and return the items in their + * original order. + */ + log.error("could not order entities because of lack of entity metadata; returning original ordering"); + return new ArrayList<>(items); + } + + // sort the orderable list + Collections.sort(orderableList); + + // Add the ordered results into the results collection + for (OrderableItem result : orderableList) { + results.add(result.unwrap()); + } + + return results; + } + +} diff --git a/src/test/java/uk/org/iay/incommon/dom/saml/InCommonEntityOrderingStrategyTest.java b/src/test/java/uk/org/iay/incommon/dom/saml/InCommonEntityOrderingStrategyTest.java new file mode 100644 index 0000000..0c148cd --- /dev/null +++ b/src/test/java/uk/org/iay/incommon/dom/saml/InCommonEntityOrderingStrategyTest.java @@ -0,0 +1,78 @@ + +package uk.org.iay.incommon.dom.saml; + +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.Item; +import net.shibboleth.metadata.ItemId; +import net.shibboleth.metadata.dom.DOMElementItem; +import net.shibboleth.metadata.dom.saml.mdrpi.RegistrationAuthority; +import uk.org.ukfederation.mda.BaseDOMTest; + +public class InCommonEntityOrderingStrategyTest extends BaseDOMTest { + + protected InCommonEntityOrderingStrategyTest() { + super(InCommonEntityOrderingStrategy.class); + } + + private Item makeItem(String registrar, String entityID) throws Exception { + final Item item = new DOMElementItem(readXmlData("trivial.xml")); + item.getItemMetadata().put(new ItemId(entityID)); + item.getItemMetadata().put(new RegistrationAuthority(registrar)); + return item; + } + + @Test + public void testOrder() throws Exception { + // distinguished registrar + final String registrar_d = "http://d"; + + // other registrars + final String registrar_1 = "http://registrar1"; + final String registrar_2 = "http://registrar2"; + + // create some items in the order they will end up + final Item i00 = makeItem(registrar_d, "entity-id-999"); + final Item i01 = makeItem(registrar_d, "entity-id-777"); + final Item i10 = makeItem(registrar_1, "entity-id-0"); + final Item i11 = makeItem(registrar_1, "entity-id-5"); + final Item i12 = makeItem(registrar_1, "entity-id-9"); + final Item i20 = makeItem(registrar_2, "entity-id-a"); + final Item i21 = makeItem(registrar_2, "entity-id-m"); + final Item i22 = makeItem(registrar_2, "entity-id-z"); + + // Make a collection containing those items in an arbitrary order + // Note that the registrar_d items must be in the final order + final List> items = new ArrayList<>(); + items.add(i22); + items.add(i11); + items.add(i00); + items.add(i12); + items.add(i20); + items.add(i21); + items.add(i01); + items.add(i10); + Assert.assertEquals(items.size(), 8); + + // Order the collection + final InCommonEntityOrderingStrategy strat = new InCommonEntityOrderingStrategy(registrar_d); + final List>items2 = strat.order(items); + + // Check that everything is in the right place afterwards + Assert.assertEquals(items2.size(), items.size()); + Assert.assertEquals(items2.get(0), i00, "i00"); + Assert.assertEquals(items2.get(1), i01, "i01"); + Assert.assertEquals(items2.get(2), i10, "i10"); + Assert.assertEquals(items2.get(3), i11, "i11"); + Assert.assertEquals(items2.get(4), i12, "i12"); + Assert.assertEquals(items2.get(5), i20, "i20"); + Assert.assertEquals(items2.get(6), i21, "i21"); + Assert.assertEquals(items2.get(7), i22, "i22"); + } + +} diff --git a/src/test/resources/uk/org/iay/incommon/dom/saml/InCommonEntityOrderingStrategy-trivial.xml b/src/test/resources/uk/org/iay/incommon/dom/saml/InCommonEntityOrderingStrategy-trivial.xml new file mode 100644 index 0000000..d250359 --- /dev/null +++ b/src/test/resources/uk/org/iay/incommon/dom/saml/InCommonEntityOrderingStrategy-trivial.xml @@ -0,0 +1,4 @@ + + + +