[java-identity-provider] branch maint-4.1 updated: Move WAR contents checking back to 4.1

Rod Widdowson rdw at steadingsoftware.com
Sat Jul 17 13:06:26 UTC 2021


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

rdw pushed a commit to branch maint-4.1
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=193041b2cf50f173efa664c0966794f5f63ef238

The following commit(s) were added to refs/heads/maint-4.1 by this push:
       new  193041b2c Move WAR contents checking back to 4.1
193041b2c is described below

commit 193041b2cf50f173efa664c0966794f5f63ef238
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Sat Jul 17 13:39:02 2021 +0100

    Move WAR contents checking back to 4.1
    
    Test fails and so is supressed pending parent pom fixups.
---
 idp-installer/pom.xml                              |   6 +
 .../idp/dependencies/DependencyTest.java           | 461 ++++++++++++++++++
 .../net/shibboleth/idp/dependencies/ParsedPom.java | 517 +++++++++++++++++++++
 .../net/shibboleth/idp/dependencies/PomLoader.java |  36 ++
 4 files changed, 1020 insertions(+)

diff --git a/idp-installer/pom.xml b/idp-installer/pom.xml
index 850fb77f0..0ae7e43ce 100644
--- a/idp-installer/pom.xml
+++ b/idp-installer/pom.xml
@@ -180,6 +180,12 @@
             <scope>test</scope>
         </dependency>
         
+	<dependency>
+            <groupId>org.apache.maven.shared</groupId>
+            <artifactId>maven-invoker</artifactId>
+            <version>3.1.0</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <scm>
diff --git a/idp-installer/src/test/java/net/shibboleth/idp/dependencies/DependencyTest.java b/idp-installer/src/test/java/net/shibboleth/idp/dependencies/DependencyTest.java
new file mode 100644
index 000000000..123e2c901
--- /dev/null
+++ b/idp-installer/src/test/java/net/shibboleth/idp/dependencies/DependencyTest.java
@@ -0,0 +1,461 @@
+/*
+ * 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.idp.dependencies;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.maven.shared.invoker.DefaultInvocationRequest;
+import org.apache.maven.shared.invoker.DefaultInvoker;
+import org.apache.maven.shared.invoker.InvocationRequest;
+import org.apache.maven.shared.invoker.Invoker;
+import org.apache.maven.shared.invoker.MavenInvocationException;
+import org.opensaml.core.testing.OpenSAMLInitBaseTestCase;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
+import org.testng.SkipException;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.dependencies.ParsedPom.PomArtifact;
+import net.shibboleth.idp.installer.plugin.impl.PluginInstallerSupport;
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.xml.ParserPool;
+
+/**
+ * Test that what we see is what we wanted in abuild - we do this by reading the pom
+ */
+public class DependencyTest extends OpenSAMLInitBaseTestCase implements PomLoader {
+
+    /** Set this up if you want to run the tests from eclipse. */
+    private static String LOCAL_MAVEN_HOME = null;
+
+    /** A list of things which get added to real versions. */
+    private static final List<String> extensionGarnish = List.of("-SNAPSHOT", "-GA", "-jre", "-empty-to-avoid-conflict-with-guava");
+
+    /** Parse for us to use. */
+    private ParserPool parserPool;
+    
+    /** Work space.  Deleted on exit. */
+    private Path workingDir;
+    
+    /** The parsed idp-parent pom. */
+    private ParsedPom idpParent;
+    
+    private PrintWriter report;
+
+    private PomArtifact parentArtefact;
+    
+    /**  We have as an assumption that the CWD is idp-installer.  Test this.
+     * @throws IOException if the directory isn't what we expect it to be
+     */
+    @BeforeClass public void testWorkingDir() throws IOException {
+        final Path path = Path.of(".");
+        final String myPath = path.toFile().getCanonicalPath();
+        final String indirectPath = path.resolve("..").resolve("idp-installer").toFile().getCanonicalPath();
+
+        assertTrue(path.resolve("..").resolve("idp-war").toFile().exists());
+        assertEquals(myPath, indirectPath);
+    }
+    
+    /** Set up maven.
+     * This relies on a couple of dodgy tests when running from maven from the command line and
+     * on the user setting up {@link #LOCAL_MAVEN_HOME} when running from eclipse.
+     */
+    @BeforeClass public void setupMavenEnvironment() {
+        if (System.getProperty("maven.home") != null) {
+            return;
+        }
+        String home = LOCAL_MAVEN_HOME;
+        if (home == null) {
+            home = System.getenv("MAVEN_HOME");
+        }
+        if (home == null) {
+            home = System.getenv("_");
+        }
+        if (home == null) {
+            throw new SkipException("Maven Not Located");
+        }
+
+        System.setProperty("maven.home", home);
+    }
+    
+    /** Parse the idp-parent pom and all related.
+     * @throws Exception if a folder or files has issues, 
+     *    if the pom is badly formed, or if the download fails
+     */
+
+    @BeforeClass(dependsOnMethods = {"setupMavenEnvironment", "testWorkingDir"}) public void parsePom() throws Exception {
+        workingDir = Files.createTempDirectory("dependencyTest");
+        parserPool = XMLObjectProviderRegistrySupport.getParserPool();
+
+        idpParent = new ParsedPom(parserPool, this, Path.of("../idp-parent/pom.xml"), "idp-parent/pom.xml", null, Collections.emptyMap());
+        parentArtefact = idpParent.getParent(); 
+        assertNotNull(parentArtefact);
+        final Path parentPath = downloadPom(parentArtefact);
+        final ParsedPom projectParent = new ParsedPom(parserPool, this, parentPath, "parent/pom.xml", new Properties(), Collections.emptyMap());
+        idpParent = new ParsedPom(parserPool, this, Path.of("../idp-parent/pom.xml"), "idp-parent/pom.xml", projectParent.getProperties(), projectParent.getManagedDependencies());
+        assertTrue(projectParent.getCompileDependencies().isEmpty(), "project parent contributes compile dependencies");
+        assertTrue(projectParent.getRuntimeDependencies().isEmpty(), "project parent contributes run time dependencies");
+    }
+
+    /** Create the reporter print stream
+     * @throws FileNotFoundException  if we cannot
+     */
+    @BeforeClass(dependsOnMethods = {"testWorkingDir"}) public void initializeOutput() throws FileNotFoundException {
+        final File out = new File("target/dependencyReport.txt");
+        final FileOutputStream outStream = new FileOutputStream(out);
+        report = new PrintWriter(new BufferedOutputStream(outStream));
+        report.format("Dependency Analysis, started at %s\n", Instant.now().toString());
+    }
+
+    /** Clean up after ourselves. */
+    @AfterClass public void teardown() {
+        PluginInstallerSupport.deleteTree(workingDir);
+    }
+
+    /** The guts of the first test.  Are all the files what we expected?
+     * @throws IOException if the file doesn't exist
+     * @throws MavenInvocationException if we fail to download a pom or a dependency
+     */
+    @Test(enabled=false) public void testDependencies() throws IOException, MavenInvocationException {
+        if (!idpParent.getDuplicates().isEmpty()) {
+            report.format("Duplicates found parsing the poms\n");
+            for (final Pair<PomArtifact,PomArtifact> poms : idpParent.getDuplicates()) {
+                final PomArtifact f = poms.getFirst();
+                final PomArtifact s = poms.getSecond();
+
+                report.format("%-22s\t: %s (from %s) and %s (from %s)\n", f.getMapKey(),
+                        f.getVersion(), f.getSourcePomFilename(),
+                        s.getVersion(), s.getSourcePomFilename());
+            }
+        }
+        final Path lib = Path.of("../idp-war-distribution/target/idp-war-distribution-"+ idpParent.getOurInfo().getVersion()).resolve("WEB-INF").resolve("lib");
+	if (!Files.exists(lib)) {
+	     throw new SkipException("War distribution target not found");
+	}
+        final Map<String, String> names = new HashMap<>();
+        int wrongVersion = 0;
+        int found = 0;
+        int nonUsed = 0;
+        int dupNames = 0;
+        final int similarNames = Files.list(lib).mapToInt(e -> addName(names, lib.relativize(e).toString())).sum();
+        report.format("Dependencies found in war file\n\n");
+
+        List<PomArtifact> dependencies = new ArrayList<>(idpParent.getCompileDependencies().size() + idpParent.getRuntimeDependencies().size());
+        dependencies.addAll(idpParent.getCompileDependencies());
+        dependencies.addAll(idpParent.getRuntimeDependencies());
+        Collections.sort(dependencies);
+        // ArtifactId->(Ver->[source, source])
+        final Map<String, Map<String, Set<String>>> dependencySource = new HashMap<>();
+        PomArtifact last = null;
+        for (PomArtifact artifact : dependencies) {
+            final String id = artifact.getArtifactId();
+            final String ver = artifact.getVersion();
+            final String sourcePomFilename = "(from " + artifact.getSourcePomFilename() + ")";
+            final String version = names.remove(id);
+            if (idpParent.getGeneratedArtifacts().contains(artifact)) {
+                if (!artifact.equals(last)) {
+                    report.format("%-22s\t: %-12s\tGenerated by parent war\n", id, ver);
+                }
+            } else if (artifact.equals(last)) {
+                report.format("%-22s\t: %-12s\tRuntime & Compile: %-22s\n", id, ver, sourcePomFilename);
+                dupNames++;
+            } else if (version == null) {
+                report.format("%-22s\t: %-12s\tNot found in war    %-22s\n", id, ver, sourcePomFilename);
+                nonUsed++;
+            } else if (version.equals(ver)) {
+                report.format("%-22s\t: %-12s\tFound in war        %-22s\n", id, ver, sourcePomFilename);
+                found++;
+                analyzeChild(dependencySource, artifact);
+            } else {
+                report.format("%-22s\t: %-12s\tVersion Mismatch- found %s %s\n", id, ver, version, sourcePomFilename);
+                analyzeChild(dependencySource, artifact.withVersion(version));
+                if (!ver.equals(PomArtifact.BAD_VERSION)) {
+                    wrongVersion++;
+                }
+            }
+            last = artifact;
+        }
+        if (dupNames != 0) {
+            report.format("\n%d Duplicate names\n", dupNames); 
+        }
+        if (similarNames != 0) {
+            report.format("\n%d Artifacts with multiple versions\n", similarNames);
+        }
+
+        report.format("\n%d dependencies, %d found, %d not found, %d mismatched\n\nDependency Sources\n", dependencies.size(), found, nonUsed, wrongVersion);
+
+        final List<String> contributedDeps = new ArrayList<>(names.keySet());
+        Collections.sort(contributedDeps);
+        int noSource = 0;
+
+        report.format("Found in but not explicitly defined as a dependency:\n\n");
+
+        for (final String dependency: contributedDeps) {
+            final Map<String, Set<String>> map = dependencySource.get(dependency);
+            final String version = names.get(dependency);
+            if (map == null) {
+                if (!dependency.startsWith("idp-")) {
+                    report.format("%-22s\t: %-12s\tNo source artefact found\n", dependency, version);
+                    noSource++;
+                }
+            } else {
+                final Set<String> sources = map.remove(version);
+                if (sources == null) {
+                    report.format("%-22s\t: %-12s\tNO Dependency contributes this version\n", dependency, version);
+                    noSource ++;
+                } else {
+                    reportContributions(dependency, version, sources);
+                }
+                final List<String> versions = new ArrayList<>(map.keySet());
+                Collections.sort(versions);
+                for (final String ver:versions) {
+                    reportContributions(dependency, ver, map.get(ver));
+                }
+            }
+        }
+        report.format("%d Orphans artifact(s)\n", noSource);
+        report.format("%d Similar artifact names(s)\n", similarNames);
+        report.format("%d Wrong Versions(s)\n", wrongVersion);
+        report.format("Completed at %s\n", Instant.now().toString());
+        report.flush();
+        report.close();
+        assertEquals(wrongVersion,  0, "Mismatched version");
+        assertEquals(similarNames,  0, "Multiple similarly named jars");
+        assertEquals(noSource,  0, "Orphaned Artefacts");
+        assertTrue(idpParent.getDuplicates().isEmpty(), "Duplicate dependencies");
+    }
+    
+    /** report the contributions of the provided dependency & version.
+     * @param dependency the artifact ID  
+     * @param version the version we are considering
+     * @param sources what caused this to exist
+     */
+    private void reportContributions(final String dependency, final String version, final Collection<String> sources) {
+        List<String> srcs = new ArrayList<>(sources);
+        Collections.sort(srcs);
+        report.format("%-22s\t: %-12s\tContributed by ", dependency, version);
+        for (int i = 0; i < (srcs.size()-1); i++) {
+            report.format("%s,", srcs.get(i));
+            if ((i&3)==3) {
+                report.format("\n                                      \t");
+            }
+        }
+        report.format("%s\n", srcs.get(srcs.size()-1));
+    }
+
+    /** Given an artifact do an "mvn dependency:copy-dependencies" on it.
+     * Then analyse the output file into the map.  The dependency name
+     * yields a map.  Looking this up with a version yields a set of the sources.
+     * @param dependencySource where to accumulate the results
+     * @param artifact what to start with.
+     * @throws MavenInvocationException  if maven fails.
+     * @throws IOException if a file doesbn't exist.
+     */
+    private void analyzeChild(final Map<String, Map<String, Set<String>>> dependencySource,
+            final PomArtifact artifact) throws MavenInvocationException, IOException {
+        final File pomFile = outputPom(artifact);
+        final String artifactName = artifact.getArtifactId()+"-"+artifact.getVersion();
+        final Path outputDir = workingDir.resolve(artifactName);
+
+        final Properties props = new Properties(2);
+        props.setProperty("includeScope","runtime");
+        props.setProperty("outputDirectory", outputDir.toString());
+        InvocationRequest request = new DefaultInvocationRequest().setProperties(props).setPomFile(pomFile).setGoals( Arrays.asList( "dependency:copy-dependencies" ) );
+
+        Invoker invoker = new DefaultInvoker();
+        invoker.execute( request );
+        if (Files.exists(outputDir)) {
+            Files.list(outputDir).forEach(e -> addDep(dependencySource, outputDir.relativize(e).toString(), artifact));
+        }
+    }
+
+    /** Add the artifact as a source of this file.
+     * @param dependencySources where to accumulate the answers 
+     * @param dep the file name of the dependency which was down-loaded
+     * @param artifact the artifact which provoked the download
+     */
+    private void addDep(final Map<String, Map<String, Set<String>>> dependencySources,
+            final String dep,
+            final PomArtifact artifact) {
+        final Pair<String,String> depId = splitFileName(dep);
+        
+        if (artifact.getArtifactId().equals(depId.getFirst()) && artifact.getVersion().equals(depId.getSecond())) {
+            // it's us.  Not interesting
+            return;
+        }
+        
+        // for each version, what contributed this dependency
+        Map<String, Set<String>> depEntry = dependencySources.get(depId.getFirst());
+        if (depEntry == null) {
+            depEntry = new HashMap<>();
+            dependencySources.put(depId.getFirst(), depEntry);
+        }
+        Set<String> provider = depEntry.get(depId.getSecond());
+        if (provider == null) {
+            provider = new HashSet<>();
+            depEntry.put(depId.getSecond(), provider);
+        }
+        provider.add(artifact.getArtifactId()+"-"+artifact.getVersion());
+    }
+
+    /** Create a pom file which has one dependency - this artifact.
+     * @param artifact the artifact.
+     * @return the file.
+     * @throws FileNotFoundException if the created pom file doesnt exist?
+     */
+    private File outputPom(PomArtifact artifact) throws FileNotFoundException {
+        final File file = workingDir.resolve(new StringBuilder(artifact.getArtifactId())
+                .append("-")
+                .append(artifact.getVersion())
+                .append(".xml").
+                toString()).toFile();
+        try (final PrintWriter pom = new PrintWriter(new BufferedOutputStream(new FileOutputStream(file)))) {
+            pom.format("<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+                    + "     xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n"
+                    + "    <modelVersion>4.0.0</modelVersion>\n"
+                    + "\n"
+                    + "    <parent>\n"
+                    + "        <groupId>%s</groupId>\n"
+                    + "        <artifactId>%s</artifactId>\n"
+                    + "        <version>%s</version>\n"
+                    + "    </parent>\n"
+                    + "\n", parentArtefact.getGroupId(), parentArtefact.getArtifactId(), parentArtefact.getVersion());
+            pom.format("    <groupId>shibboleth.net.dependency</groupId>\n"
+                    + "    <version>0.0.1</version>\n"
+                    + "    <name>Shibboleth Dependency</name>\n"
+                    + "    <artifactId>idp-dep-%s</artifactId>\n"
+                    + "    <packaging>jar</packaging>\n\n", artifact.getArtifactId());
+            pom.format("    <dependencies>\n"
+                    + "    <dependency>\n"
+                    + "            <groupId>%s</groupId><artifactId>%s</artifactId><version>%s</version>\n"
+                    + "    </dependency>\n"
+                    + "    </dependencies>\n\n", artifact.getGroupId(),artifact.getArtifactId(), artifact.getVersion());
+            pom.format("    <repositories>\n"
+                    + "        <repository>\n"
+                    + "            <id>shib-release</id>\n"
+                    + "            <url>https://build.shibboleth.net/nexus/content/groups/public</url>\n"
+                    + "            <snapshots>\n"
+                    + "                <enabled>false</enabled>\n"
+                    + "            </snapshots>\n"
+                    + "        </repository>\n"
+                    + "        <repository>\n"
+                    + "            <id>shib-snapshot</id>\n"
+                    + "            <url>https://build.shibboleth.net/nexus/content/repositories/snapshots</url>\n"
+                    + "            <releases>\n"
+                    + "                <enabled>false</enabled>\n"
+                    + "            </releases>\n"
+                    + "        </repository>\n"
+                    + "    </repositories>\n"
+                    + "</project>\n");
+            pom.flush();
+            pom.close();
+        }
+        return file;
+    }
+
+    /** Split the file name into the artifact (first) and version (second).
+     * @param inName the file name
+     * @return a pair.
+     */
+    private Pair<String, String> splitFileName(final String inName) {
+        final String name;
+        if (inName.endsWith(".jar")) {
+            name = inName.substring(0, inName.length()-4);
+        } else {
+            name = inName;
+        }
+        int last = name.lastIndexOf("-");
+        for (String otherGarnish : extensionGarnish) {
+            if (name.endsWith(otherGarnish)) {
+                last = name.substring(0, name.length()-otherGarnish.length()).lastIndexOf("-");
+                break;
+            }
+        }
+        final String base = name.substring(0, last);
+        String versionExtension = name.substring(last+1);
+        return new Pair<>(base, versionExtension);
+    }
+
+    /** Trivial accumulator to pull a name in the lib directory apart and insert it into the map.
+     * @param names The map to accumulate into
+     * @param jarPath the file we are looking at.
+     * @return 1 if there as a artifact with the same name.
+     */
+    private int addName(Map<String, String> names, String jarPath) {
+        final Pair<String, String> nm = splitFileName(jarPath);
+        final String oldName = names.put(nm.getFirst(), nm.getSecond());
+        if (oldName == null) {
+            return 0;
+        }
+        return 1;
+    }
+
+    /** Tell Maven to download the artifact and returns it's path.
+     * @param artifact what to look for
+     * @return the pom as a {@link Path}
+     * @throws MavenInvocationException if the download failed
+     */
+    public Path downloadPom(final PomArtifact artifact) throws MavenInvocationException {
+        final Path output =  workingDir.resolve(artifact.getArtifactId() + ".pom");
+        assertFalse(Files.exists(output));
+        final String fullArtifactName = new StringBuilder(artifact.getGroupId())
+                    .append(':')
+                    .append(artifact.getArtifactId())
+                    .append(':')
+                    .append(artifact.getVersion())
+                    .append(":pom")
+                    .toString();
+        
+        final Properties props = new Properties(3);
+        props.setProperty("artifact",fullArtifactName);
+        props.setProperty("mdep.stripVersion","true");
+        props.setProperty("outputDirectory", workingDir.toString());
+       
+        InvocationRequest request = new DefaultInvocationRequest().setProperties(props).setGoals( Arrays.asList( "dependency:copy" ) );
+
+        Invoker invoker = new DefaultInvoker();
+        invoker.execute( request );
+        assertTrue(Files.exists(output));
+        
+        return output;
+    }
+}
diff --git a/idp-installer/src/test/java/net/shibboleth/idp/dependencies/ParsedPom.java b/idp-installer/src/test/java/net/shibboleth/idp/dependencies/ParsedPom.java
new file mode 100644
index 000000000..ba0bc12f6
--- /dev/null
+++ b/idp-installer/src/test/java/net/shibboleth/idp/dependencies/ParsedPom.java
@@ -0,0 +1,517 @@
+/*
+ * 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.idp.dependencies;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.core.testing.OpenSAMLInitBaseTestCase;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import com.beust.jcommander.internal.Nullable;
+
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+import net.shibboleth.utilities.java.support.xml.ElementSupport;
+import net.shibboleth.utilities.java.support.xml.ParserPool;
+import net.shibboleth.utilities.java.support.xml.XMLParserException;
+
+/**
+ *
+ */
+public class ParsedPom extends OpenSAMLInitBaseTestCase{
+    
+    /** Compile dependencies - what we care about. */
+    private final Map<String, PomArtifact> compileDependencies = new HashMap<>();
+    
+    /** BOM dependencies. */
+    private final Map<String, PomArtifact> bomDependencies = new HashMap<>();
+
+    /** Rum time dependencies. */
+    private final Map<String, PomArtifact> runtimeDependencies = new HashMap<>();    
+
+    /** Duplicate dependencies. */
+    private final List<Pair<PomArtifact,PomArtifact>> duplicates = new ArrayList<>();    
+
+    /** Generated artifacts. */
+    private final Set<PomArtifact> generated = new HashSet<>();
+
+    /** Inherits dependencies. */
+    private final Map<String, PomArtifact> managedDependencies;
+    
+    /** Which the POM.*/
+    @Nonnull private final String sourcePomInfo;
+    
+    /** Properties. */
+    private final Properties properties = new Properties();
+    
+    /** Parent Pom .*/
+    private PomArtifact parent;
+    
+    /** Us. */
+    private final PomArtifact us;
+
+    /**
+     * Constructor.
+     *
+     * @param parsers a short-cut to let us parse XML
+     * @param pomLoader how to get a pom (for BOM loading)
+     * @param pom the {@link Path} to the pom.
+     * @param pomName an ID for the pom
+     * @param parentPomProperties if present it is properties from the parent (which might be empty), if null we are *only*
+     * looking for the parent pom coordinates.
+     * @param map Managed dependencies from parent
+     * @throws Exception if we have issued locating a bom
+     */
+    public ParsedPom(@Nonnull final ParserPool parsers,
+                     @Nonnull final PomLoader pomLoader,
+                     @Nonnull final Path pom,
+                     @Nonnull final String pomName,
+                     @Nullable final Properties parentPomProperties, 
+                     @Nonnull final Map<String, PomArtifact> map)
+            throws Exception {
+
+        managedDependencies = new HashMap<>(map);
+
+        sourcePomInfo = pomName;
+        Document document;
+        try (final InputStream stream = new BufferedInputStream(new FileInputStream(pom.toFile()))) {
+            document = parsers.parse(stream);
+        }
+
+        final Element el = document.getDocumentElement();
+        if (!"project".equals(el.getLocalName())) {
+            throw new XMLParserException("Top level element was not <project>");
+        }
+        final List<Element> par = ElementSupport.getChildElementsByTagName(el, "parent");
+        
+        if (!par.isEmpty()) {
+            parseParent(par.get(0));
+        }
+
+        us = new PomArtifact(el, parent);
+
+        if (parentPomProperties == null) {
+            return;
+        }
+        for (final Object p:parentPomProperties.keySet()) {
+            String pName = (String) p;
+            properties.setProperty(pName, parentPomProperties.getProperty(pName));
+        }
+        properties.setProperty("project.basedir", "<bogus_base_dir>");
+        properties.setProperty("project.build.directory", "<bogus_build_dir>");
+        properties.setProperty("project.version", us.getVersion());
+        properties.setProperty("project.groupId", us.getGroupId());
+        properties.setProperty("project.artifactId", us.getArtifactId());
+
+        final List<Element> props = ElementSupport.getChildElementsByTagName(el, "properties");
+        if (!props.isEmpty()) {
+            parseProperties(props.get(0));
+        }
+
+        for (final Element dependencyMgt: ElementSupport.getChildElementsByTagName(el, "dependencyManagement")) {
+            for (final Element dependencies : ElementSupport.getChildElementsByTagName(dependencyMgt, "dependencies")) {
+                parseManagedDependencies(dependencies);
+            }
+        }
+        for (final PomArtifact bom : bomDependencies.values()) {
+            final ParsedPom parsedBom = new ParsedPom(parsers, pomLoader, pomLoader.downloadPom(bom), bom.toString(), new Properties(), Collections.emptyMap());
+            for (PomArtifact dep : parsedBom.getManagedDependencies().values()) {
+                addWithCheck(dep, managedDependencies);
+            }
+        }
+
+        for (final Element dependencies : ElementSupport.getChildElementsByTagName(el, "dependencies")) {
+            parseDependencies(dependencies);
+        }
+
+        final Set<PomArtifact> moduleCompiles = new HashSet<>();
+        final Set<PomArtifact> moduleRuntimes = new HashSet<>();
+        for (final Element modules: ElementSupport.getChildElementsByTagName(el, "modules")) {
+            for (final Element module: ElementSupport.getChildElementsByTagName(modules, "module")) {
+                // Kludge for Jackson
+                final Path modulePath = Path.of(module.getTextContent()).resolve("pom.xml");
+                if (Files.exists(modulePath)) {
+                    final ParsedPom modulePom = new ParsedPom(parsers, pomLoader, modulePath ,module.getTextContent(), properties, managedDependencies);
+                    moduleCompiles.addAll(modulePom.getCompileDependencies());
+                    moduleRuntimes.addAll(modulePom.getRuntimeDependencies());
+                    generated.add(modulePom.getOurInfo());
+                }
+            }
+        }
+        for (final PomArtifact dep : moduleCompiles) {
+            addWithCheck(dep, compileDependencies);
+        }
+        for (final PomArtifact dep : moduleRuntimes) {
+            addWithCheck(dep, runtimeDependencies);
+        }
+    }
+
+    /** Get the text content of the element, performing property replacement as we go.
+     * @param el the element
+     * @return the value, with property replacement.
+     */
+    @Nonnull protected String getElementContent(final Element el) {
+        String remainingContents = StringSupport.trimOrNull(el.getTextContent());
+        remainingContents = Constraint.isNotNull(remainingContents, "<" + el.getLocalName() +  "> must have content");
+        final StringBuilder contents = new StringBuilder();
+        for (int index = remainingContents.indexOf("${"); index >= 0; index = remainingContents.indexOf("${")) {
+            contents.append(remainingContents.substring(0, index));
+            remainingContents = remainingContents.substring(index);
+            final int endIndex = remainingContents.indexOf("}");
+            if (endIndex <= 1) {
+                break;
+            }
+            final String propName = remainingContents.substring(2, endIndex);
+            contents.append(Constraint.isNotNull(properties.getProperty(propName), propName + " is not defined"));
+            remainingContents = remainingContents.substring(endIndex+1);
+        }
+        contents.append(remainingContents);
+        return contents.toString();
+    }
+
+    /** Parse the dependency part of the pom.
+     * @param item what to parse
+     */
+    private void parseDependencies(final Element item) {
+        final List<Element> dependencies = ElementSupport.getChildElementsByTagName(item, "dependency");
+        
+        for (Element dependency : dependencies) {
+            final PomArtifact artifact = new PomArtifact(dependency);
+            final List<Element> types = ElementSupport.getChildElementsByTagName(dependency, "type");
+            if (!types.isEmpty()) {
+                final String type = StringSupport.trimOrNull(types.get(0).getTextContent());
+                if ("pom".equals(type)) {
+                    addWithCheck(artifact, bomDependencies);
+                    continue;
+                } else if (!"jar".equals(type)) {
+                    // not for us
+                    continue;
+                }                
+            }
+            final List<Element> scopes = ElementSupport.getChildElementsByTagName(dependency, "scope");
+            if (!scopes.isEmpty()) {
+                final String scope = StringSupport.trimOrNull(scopes.get(0).getTextContent());
+                if ("runtime".equals(scope)) {
+                    addWithCheck(artifact, runtimeDependencies);
+                    continue;
+                }
+                if (!"compile".equals(scope)) {
+                    // not for us
+                    continue;
+                }
+            }
+            addWithCheck(artifact, compileDependencies);
+            continue;
+        }
+    }
+
+    /** parse the Managed Dependencies from the provided item
+     * @param item  what to parse
+     */
+    private void parseManagedDependencies(final Element item) {
+        final List<Element> dependencies = ElementSupport.getChildElementsByTagName(item, "dependency");
+        for (Element dependency : dependencies) {
+            final PomArtifact artifact = new PomArtifact(dependency);
+            final List<Element> types = ElementSupport.getChildElementsByTagName(dependency, "type");
+            if (!types.isEmpty()) {
+                final String type = StringSupport.trimOrNull(types.get(0).getTextContent());
+                if ("pom".equals(type)) {
+                    addWithCheck(artifact, bomDependencies);
+                    continue;
+                } else if (!"jar".equals(type)) {
+                    // not for us
+                    continue;
+                }
+            }
+            addWithCheck(artifact, managedDependencies);
+        }
+    }
+
+    /** Add the artifact to the map, accumulating duplicates.
+     * @param artifact what to add
+     * @param map wghere to add it
+     */
+    private void addWithCheck(final PomArtifact artifact, final Map<String, PomArtifact> map) {
+        final PomArtifact old = map.put(artifact.getMapKey(),artifact);
+        if (old != null) {
+            duplicates.add(new Pair<>(old, artifact));
+        }
+    }
+
+    /** Parse the properties from the pom.
+     * @param item the <properties> element
+     */
+    private void parseProperties(Element item) {
+        
+        for (final Element child : ElementSupport.getChildElements(item)) {
+            final String name = child.getLocalName();
+            final String value = getElementContent(child);
+            
+            properties.setProperty(name, value);
+        }
+    }
+
+    /** Parse the parent from the pom. 
+     * @param item the <parent> element
+     */
+    private void parseParent(Element item) {
+        parent = new PomArtifact(item);
+    }
+
+    /** Returns the Compile Dependencies.
+     * @return Returns the Compile Dependencies.
+     */
+    @Nonnull public Collection<PomArtifact> getCompileDependencies() {
+        return compileDependencies.values();
+    }
+
+    /** Returns the Runtime Dependencies.
+     * @return Returns the Runtime Dependencies.
+     */
+    @Nonnull public Collection<PomArtifact> getRuntimeDependencies() {
+        return runtimeDependencies.values();
+    }
+
+    /**  Returns the Managed Dependencies.
+     * @return Returns the Managed Dependencies.
+     */
+    @Nonnull public Map<String, PomArtifact> getManagedDependencies() {
+        return managedDependencies;
+    }
+
+    /** Get artifacts that were duplicated by this build
+     * @return Returns the duplicates.
+     */
+    @Nonnull public List<Pair<PomArtifact, PomArtifact>> getDuplicates() {
+        return duplicates;
+    }
+
+    /** returns any sub modules created by this module.
+     * @return Returns the generated.
+     */
+    @Nonnull public Set<PomArtifact> getGeneratedArtifacts() {
+        return generated;
+    }
+
+    /** Return our artifactInformation.
+     * @return us.
+     */
+    public PomArtifact getOurInfo() {
+        return us;
+    }
+
+    /** Return the parent.
+     * @return  the parent.
+     */
+    public PomArtifact getParent() {
+        return parent;
+    }
+
+    /** The <properties> contents.
+     * @return Returns the properties.
+     */
+    public Properties getProperties() {
+        return properties;
+    }
+
+    /** Encapsulation of a <dependency> element. */
+    public class PomArtifact implements Comparable<PomArtifact>{
+
+        /** What version to give if we cannot find the version. */
+        public final static String BAD_VERSION = "VERSION_NOT_DETERMINED"; 
+
+        /** <groupId>.*/
+        @Nonnull private final String groupId;
+
+        /** <artifactId>.*/
+        @Nonnull private final String artifactId;
+        
+        /** <version>.*/
+        @Nonnull private final String version;
+
+        /** <exclusions>. */
+        @Nonnull private final Set<Pair<String, String>> exclusions = new HashSet<>();
+
+        /**
+         * Constructor.
+         *
+         * @param id the <artifactId> 
+         * @param group the <groupId>
+         * @param ver the <version>
+         */
+        private PomArtifact(final String id, final String group, final String ver) {
+            artifactId = id;
+            groupId = group;
+            version = ver;
+        }
+        
+        /**
+         * Constructor.
+         *
+         * @param item element to interrogate.
+         */
+        public PomArtifact(final Element item) {
+            this(item, null);
+        }
+
+        /**
+         * Constructor.
+         *
+         * @param item element to interrogate.
+         * @param parentArtifact to inherit from
+         */
+        public PomArtifact(final Element item, final @Nullable PomArtifact parentArtifact) {
+            
+            final List<Element> grps  = ElementSupport.getChildElementsByTagName(item, "groupId");
+            if (grps.size() > 0) {
+                groupId = getElementContent(grps.get(0));
+            } else if (parentArtifact != null) {
+                groupId = parentArtifact.getGroupId();
+            } else {
+                Constraint.isGreaterThan(0, grps.size(), "<groupId> should exist in dependency");
+                groupId = null;
+            }
+            
+            final List<Element> arts  = ElementSupport.getChildElementsByTagName(item, "artifactId");
+            Constraint.isGreaterThan(0, arts.size(), "<artifactId> should exist in dependency");
+            artifactId = getElementContent(arts.get(0));
+            
+            final List<Element> vers  = ElementSupport.getChildElementsByTagName(item, "version");
+            if (vers.size() > 0) {
+                version = getElementContent(vers.get(0));
+            } else if (parentArtifact != null) {
+                version = parentArtifact.getVersion();
+            } else {
+                final PomArtifact inherited = managedDependencies.get(groupId+"+"+artifactId);
+                if (inherited != null) {
+                    version = inherited.getVersion();
+                } else {
+                    version = BAD_VERSION;
+                }
+            }
+            
+            List<Element> excls  = ElementSupport.getChildElementsByTagName(item, "exclusions"); 
+            if (excls.size() > 0) {
+                excls  = ElementSupport.getChildElementsByTagName(excls.get(0), "exclusion");
+                for (Element e : excls) {
+                    List<Element> els = ElementSupport.getChildElementsByTagName(e, "groupId");
+                    Constraint.isGreaterThan(0, els.size(), "<groupId> should exist in exclusion");
+                    final String grp = getElementContent(els.get(0));
+                    els = ElementSupport.getChildElementsByTagName(e, "artifactId");
+                    Constraint.isGreaterThan(0, els.size(), "<artifactId> should exist in exclusion");
+                    final String art = getElementContent(els.get(0));
+                    exclusions.add(new Pair<>(grp, art));
+                }
+            }
+        }
+
+        /**
+         * @return Returns the groupId.
+         */
+        public String getGroupId() {
+            return groupId;
+        }
+
+        /**
+         * @return Returns the artifactId.
+         */
+        public String getArtifactId() {
+            return artifactId;
+        }
+
+        /**
+         * @return Returns the version.
+         */
+        public String getVersion() {
+            return version;
+        }
+        
+        /**
+         * @return the pom source.
+         */
+        public String getSourcePomFilename() {
+            return sourcePomInfo;
+        }
+        
+        /** Get the key we use in out maps.
+         * @return the key - derives from groupId and EntityId 
+         */
+        public String getMapKey() {
+            return getGroupId()+"+"+getArtifactId();
+        }
+        
+        /**
+         * @return Returns the exclusions.
+         */
+        public Set<Pair<String, String>> getExclusions() {
+            return exclusions;
+        }
+
+        /** {@inheritDoc} */
+        public int compareTo(final PomArtifact o) {
+            return getArtifactId().compareTo(o.getArtifactId());
+        }
+        
+        /** {@inheritDoc} */
+        public boolean equals(final Object obj) {
+            if (obj != null && obj instanceof PomArtifact ) {
+                final PomArtifact him = (PomArtifact) obj;
+                return  him.getArtifactId().equals(getArtifactId()) &&
+                        him.getGroupId().equals(getGroupId()) &&
+                        him.getVersion().equals(getVersion());
+            }
+            return false;
+        }
+        
+        /** {@inheritDoc} */
+        public int hashCode() {
+            return Objects.hash(artifactId, groupId, version);
+        }
+        
+        /** {@inheritDoc} */
+        public String toString() {
+            return artifactId + "-" + version;
+        }
+
+        /** return the same artifact but with an amended version.
+         * @param ver the version
+         * @return an amended artifact.
+         */
+        public PomArtifact withVersion(String ver) {
+            return new PomArtifact(artifactId, groupId, ver);
+        }
+    }
+}
diff --git a/idp-installer/src/test/java/net/shibboleth/idp/dependencies/PomLoader.java b/idp-installer/src/test/java/net/shibboleth/idp/dependencies/PomLoader.java
new file mode 100644
index 000000000..a539cb71d
--- /dev/null
+++ b/idp-installer/src/test/java/net/shibboleth/idp/dependencies/PomLoader.java
@@ -0,0 +1,36 @@
+/*
+ * 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.idp.dependencies;
+
+import java.nio.file.Path;
+
+import net.shibboleth.idp.dependencies.ParsedPom.PomArtifact;
+
+/**
+ * Abstraction of a way to get hold of a pom.
+ */
+public interface PomLoader {
+
+    /** tell Something to download the artifact and returns it's path.
+     * @param artifact what to look for
+     * @return the pom as a {@link Path}
+     * @throws Exception 
+     */
+    Path downloadPom(PomArtifact artifact) throws Exception;
+
+}
\ No newline at end of file

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


More information about the commits mailing list