[java-identity-provider] 02/02: IDP-1711 Add a simple program to look for duplicate jars/classes

Rod Widdowson rdw at steadingsoftware.com
Wed Nov 18 16:33:55 UTC 2020


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

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

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

commit 08f7f026034af21eeed1a858963d38cb1938a287
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Wed Nov 18 16:33:11 2020 +0000

    IDP-1711 Add a simple program to look for duplicate jars/classes
    
    https://issues.shibboleth.net/jira/browse/IDP-1711
---
 idp-admin-impl/pom.xml                             |   8 +-
 .../idp/admin/impl/JarCheckArguments.java          |  83 +++++++
 .../net/shibboleth/idp/admin/impl/JarCheckCLI.java | 274 +++++++++++++++++++++
 idp-installer/pom.xml                              |   1 -
 4 files changed, 364 insertions(+), 2 deletions(-)

diff --git a/idp-admin-impl/pom.xml b/idp-admin-impl/pom.xml
index 162baa7e2..ed3ab5ab6 100644
--- a/idp-admin-impl/pom.xml
+++ b/idp-admin-impl/pom.xml
@@ -130,9 +130,15 @@
         <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>javax.servlet-api</artifactId>
-            <scope>provided</scope>
         </dependency>
 
+        <dependency>
+            <!--  Only used in a command line --> 
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-compress</artifactId>
+            <scope>provided</scope>
+         </dependency>
+
         <!-- Runtime Dependencies -->
         <!--
         These are strictly correct due to the embedded flows,
diff --git a/idp-admin-impl/src/main/java/net/shibboleth/idp/admin/impl/JarCheckArguments.java b/idp-admin-impl/src/main/java/net/shibboleth/idp/admin/impl/JarCheckArguments.java
new file mode 100644
index 000000000..01a3c01e0
--- /dev/null
+++ b/idp-admin-impl/src/main/java/net/shibboleth/idp/admin/impl/JarCheckArguments.java
@@ -0,0 +1,83 @@
+/*
+ * 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.admin.impl;
+
+import java.io.PrintStream;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.beust.jcommander.Parameter;
+
+import net.shibboleth.idp.cli.AbstractIdPHomeAwareCommandLineArguments;
+
+/**
+ * Command line arguments for {@link JarCheckCLI}.
+ */
+public class JarCheckArguments extends AbstractIdPHomeAwareCommandLineArguments {
+
+    /** Logger. */
+    @Nullable private Logger log;
+
+    /** look inside the jars. */
+    @Parameter(names= {"-d", "--detailed"})
+    @Nullable private boolean detailed;
+
+    /** provide a sorted list. */
+    @Parameter(names= {"-l", "--list"})
+    @Nullable private boolean list;
+
+    /** {@inheritDoc} */
+    @Nonnull public Logger getLog() {
+        if (log == null) {
+            log = LoggerFactory.getLogger(JarCheckArguments.class);
+        }
+        return log;
+    }
+    
+    /** Are we doing an in depth survey?.
+     * @return Returns detailed.
+     */
+    public boolean isDetailed() {
+        return detailed;
+    }
+    
+    /** are we listing everything?
+     * @return Returns list.
+     */
+    public boolean isList() {
+        return list;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void printHelp(final PrintStream out) {
+        out.println("JarCheck");
+        out.println("Provides a command line interface to look for duplicates in an installation");
+        out.println();
+        out.println("   XXXX [options] ");
+        out.println();
+        super.printHelp(out);
+        out.println();
+        out.println(String.format("  %-22s %s", "-l, --list", "Output all modules in sorted order"));
+        out.println(String.format("  %-22s %s", "-d, --details", "Do class level analysis"));
+    }
+}
diff --git a/idp-admin-impl/src/main/java/net/shibboleth/idp/admin/impl/JarCheckCLI.java b/idp-admin-impl/src/main/java/net/shibboleth/idp/admin/impl/JarCheckCLI.java
new file mode 100644
index 000000000..75c7d4d3c
--- /dev/null
+++ b/idp-admin-impl/src/main/java/net/shibboleth/idp/admin/impl/JarCheckCLI.java
@@ -0,0 +1,274 @@
+/*
+ * 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.admin.impl;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.ArchiveInputStream;
+import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.idp.Version;
+import net.shibboleth.idp.cli.AbstractIdPHomeAwareCommandLine;
+
+/**
+ * Program to check for potential jar clashes.
+ */
+public final class JarCheckCLI extends AbstractIdPHomeAwareCommandLine<JarCheckArguments> { 
+    
+    /** Logger. */
+    @Nullable private Logger log;
+
+    /** Jar files in edit-webapp. */
+    @Nonnull private List<String> webAppJars = Collections.emptyList();
+
+    /** Jar files Plugin Folder. */
+    @Nonnull private List<String> pluginJars = Collections.emptyList();
+
+    /** Jar files Distributions . */
+    @Nonnull private List<String> distJars = Collections.emptyList();
+
+    /** Populate {@link #webAppJars}, {@link #pluginJars} and {@link #distJars}. 
+     * @param args
+     */
+    private void loadJarFiles(final JarCheckArguments args) {
+        final Path idpHome = Path.of(args.getIdPHome());
+
+        distJars = listJars(idpHome.resolve("dist").resolve("webapp"));
+        pluginJars = listJars(idpHome.resolve("dist").resolve("plugin-webapp"));
+        webAppJars = listJars(idpHome.resolve("edit-webapp"));
+    }
+
+    /** return a list of all the files in the lib file below the webapp folder provided. 
+     * @param webapp folder to start at.
+     * @return the list of names as a strung
+     */
+    private List<String> listJars(final Path webapp) {
+        final Path libDir = webapp.resolve("WEB-INF").resolve("lib");
+        
+        if (!Files.exists(libDir) || !Files.isDirectory(libDir)) {
+            return Collections.emptyList();
+        }
+        
+        return Arrays.asList(libDir.toFile().list(new FilenameFilter() {
+            public boolean accept(final File dir, final String name) {
+                final String nameUpper = name.toUpperCase();
+                return nameUpper.endsWith(".JAR");
+            }
+        }));
+    }
+    
+    /** Check for the same file in two places and if asked for.
+     * output the sorted list
+     * @param args
+     */
+    private void listAndExactCheck(final JarCheckArguments args) {
+        final Map<String, String> allNames = new HashMap<>(distJars.size() + pluginJars.size() + webAppJars.size());
+        final String type = "jar called";
+
+        addAndCheck(allNames, distJars, "distribution", type);
+        addAndCheck(allNames, pluginJars, "plugins", type);
+        addAndCheck(allNames, webAppJars, "edit-webapp", type);
+        
+        if (!args.isList()) {
+            return;
+        }
+        
+        final List<String> names = new ArrayList<>(allNames.keySet());
+        Collections.sort(names);
+        System.out.println("Sorted List of jars");
+        System.out.println(String.format("  %-22s %s", "Name", "Source"));
+        for (final String jarName: names) {
+            System.out.println(String.format("  %-22s %s", jarName, allNames.get(jarName)));            
+        }
+    }
+
+    /** Add the names to the Map, checking for already existing.
+     * @param allNames the map
+     * @param jars the names
+     * @param source where the jar came from
+     * @param type what we are checking
+     */
+    private void addAndCheck(final Map<String, String> allNames, 
+            final List<String> jars, 
+            final String source, 
+            final String type) {
+        
+        for (final String jar: jars) {
+            final String current = allNames.get(jar);
+            if (current == null) {
+                allNames.put(jar, source);
+            } else {
+                getLogger().warn("{} {} found in {} and in {}", type, jar, source, current);
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    protected Class<JarCheckArguments> getArgumentClass() {
+        return JarCheckArguments.class;
+    }
+
+    /** {@inheritDoc} */
+    protected String getVersion() {
+        return Version.getVersion();
+    }
+
+    /** {@inheritDoc} */
+    protected synchronized Logger getLogger() {
+        if (log == null) {
+            log = LoggerFactory.getLogger(JarCheckCLI.class);
+        }
+        return log;
+    }
+
+    /** Do a general test.
+     * @param args
+     */
+    private void fileNamesOnly(final JarCheckArguments args) {
+        final Map<String, String> allNames = new HashMap<>(distJars.size() + pluginJars.size() + webAppJars.size());
+        final String type = "jar fragment called";
+
+        addAndCheck(allNames, normalize(distJars), "distribution", type);
+        addAndCheck(allNames, normalize(pluginJars), "plugins", type);
+        addAndCheck(allNames, normalize(webAppJars), "edit-webapp", type);
+    }
+    
+    /** make the file names look more 'usual'.
+     * @param fileNames
+     * @return the processed names
+     */
+    private List<String> normalize(final List<String> fileNames) {
+        
+       final Pattern patt = Pattern.compile("^(.*)-[0123456789](.*)\\.jar$");
+       final List<String> result = new ArrayList<>(fileNames.size());
+       
+       for (final String name: fileNames ) {
+           final Matcher matcher = patt.matcher(name);
+           if (matcher.find()) {
+               result.add(matcher.group(1));
+           }
+       }
+       return result;
+    }
+
+    /** Do a detailed check.
+     * @param args 
+     */
+    private void detailed(final JarCheckArguments args) {
+        final Map<String, String> allNames = new HashMap<>();
+        final Path idpHome = Path.of(args.getIdPHome());
+        
+        processClassNames(allNames, idpHome.resolve("dist").resolve("webapp"), distJars);
+        processClassNames(allNames, idpHome.resolve("dist").resolve("plugin-webapp"), pluginJars);
+        processClassNames(allNames, idpHome.resolve("edit-webapp"), webAppJars);
+    }
+
+    /** Look at all the class names in the jars provided.
+     * @param namesSoFar the class names we have found
+     * @param base The directory where the names live 
+     * @param jars The Jar Names
+     */
+    private void processClassNames(final Map<String, String> namesSoFar, final Path base, final List<String> jars) {
+        final Path libdir = base.resolve("WEB-INF").resolve("lib");
+        for (final String jar: jars) {
+            processClassNames(namesSoFar, libdir.resolve(jar));
+        }
+    }
+    
+    /** List all the class names in the jar provided.
+     * @param namesSoFar the class names we have found
+     * @param jar The far file
+     */
+    private void processClassNames(final Map<String, String> namesSoFar, final Path jar) {
+        final String source = jar.toString();
+        try (final InputStream inStream = new BufferedInputStream(new FileInputStream(jar.toFile()));
+             final ArchiveInputStream classes = new ZipArchiveInputStream(inStream)) {
+            
+            ArchiveEntry entry = null;
+            while ((entry = classes.getNextEntry()) != null) {
+                if (entry.isDirectory()) {
+                    continue;
+                }
+                final String claz = entry.getName();
+                if (claz == null || claz.startsWith("META-INF") || "module-info.class".equals(claz)) {
+                    continue;
+                }
+                final String current = namesSoFar.get(claz);
+                if (current == null) {
+                    namesSoFar.put(claz, source);
+                } else {
+                    getLogger().warn("class {} found in {} and in {}", claz, source, current);
+                }
+            }
+        } catch (final FileNotFoundException  e) {
+            getLogger().error("Could not open {}", source, e);
+        } catch (final IOException e) {
+            getLogger().error("Could not process {}", source, e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int doRun(@Nonnull final JarCheckArguments args) {
+        final int ret = super.doRun(args);
+        if (ret != RC_OK) {
+            return ret;
+        }
+        loadJarFiles(args);
+        listAndExactCheck(args);
+        if (args.isDetailed()) {
+            detailed(args);
+        } else {
+            fileNamesOnly(args);
+        }
+        
+        return RC_OK;
+    }
+    
+    /**
+     * CLI entry point.
+     * 
+     * @param args arguments
+     */
+    public static void main(@Nonnull final String[] args) {
+        System.exit(new JarCheckCLI().run(args));
+    }
+
+}
diff --git a/idp-installer/pom.xml b/idp-installer/pom.xml
index 114a64654..74f1a1818 100644
--- a/idp-installer/pom.xml
+++ b/idp-installer/pom.xml
@@ -140,7 +140,6 @@
         <dependency>
           <groupId>org.apache.commons</groupId>
           <artifactId>commons-compress</artifactId>
-          <version>${commons.compress.version}</version>
           <scope>provided</scope>
         </dependency>
         

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


More information about the commits mailing list