[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