From db0e45146a8f4071c036a88c60fc37d4e10f0e57 Mon Sep 17 00:00:00 2001 From: Adel El-Beik <48713346+adelel1@users.noreply.github.com> Date: Tue, 21 Dec 2021 11:24:02 +0000 Subject: [PATCH] ENT-6494: Upgrade log4j to 2.17.0 (#6998) --- build.gradle | 2 +- .../common/logging/manifest/Manifests.java | 379 ++++++++++++++++++ .../net/corda/common/logging/CordaVersion.kt | 4 +- 3 files changed, 382 insertions(+), 3 deletions(-) create mode 100644 common/logging/src/main/java/net/corda/common/logging/manifest/Manifests.java diff --git a/build.gradle b/build.gradle index 6ae4ddd339..c183661636 100644 --- a/build.gradle +++ b/build.gradle @@ -68,7 +68,7 @@ buildscript { ext.servlet_version = '4.0.1' ext.assertj_version = '3.12.2' ext.slf4j_version = '1.7.26' - ext.log4j_version = '2.16.0' + ext.log4j_version = '2.17.0' ext.bouncycastle_version = constants.getProperty("bouncycastleVersion") ext.guava_version = constants.getProperty("guavaVersion") ext.caffeine_version = constants.getProperty("caffeineVersion") diff --git a/common/logging/src/main/java/net/corda/common/logging/manifest/Manifests.java b/common/logging/src/main/java/net/corda/common/logging/manifest/Manifests.java new file mode 100644 index 0000000000..efb9b98362 --- /dev/null +++ b/common/logging/src/main/java/net/corda/common/logging/manifest/Manifests.java @@ -0,0 +1,379 @@ +/** + * Copyright (c) 2012-2014, jcabi.com + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: 1) Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. 2) Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. 3) Neither the name of the jcabi.com nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT + * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.corda.common.logging.manifest; + +import com.jcabi.log.Logger; +import com.jcabi.manifests.ClasspathMfs; +import com.jcabi.manifests.MfMap; +import com.jcabi.manifests.Mfs; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +/** + * To a large effect this is a copy of {@code com.jcabi.manifests.Manifests} class + * with some fixes that are identified in the code with "FIX-UP" comment. + * + * Static reader of {@code META-INF/MANIFEST.MF} files. + * + * The class provides convenient methods to read + * all {@code MANIFEST.MF} files available in classpath + * and all attributes from them. + * + *

This mechanism may be very useful for transferring + * information from continuous integration environment to the production + * environment. For example, you want your site to show project version and + * the date of {@code WAR} file packaging. First, you configure + * {@code maven-war-plugin} to add this information to {@code MANIFEST.MF}: + * + *

 <plugin>
+ *  <artifactId>maven-war-plugin</artifactId>
+ *  <configuration>
+ *   <archive>
+ *    <manifestEntries>
+ *     <Foo-Version>${project.version}</Foo-Version>
+ *     <Foo-Date>${maven.build.timestamp}</Foo-Date>
+ *    </manifestEntries>
+ *   </archive>
+ *  </configuration>
+ * </plugin>
+ * + *

{@code maven-war-plugin} will add these attributes to your + * {@code MANIFEST.MF} file and the + * project will be deployed to the production environment. Then, you can read + * these attributes where it's necessary (in one of your JAXB annotated objects, + * for example) and show to users: + * + *

 import com.jcabi.manifests.Manifest;
+ * import java.text.SimpleDateFormat;
+ * import java.util.Date;
+ * import java.util.Locale;
+ * import javax.xml.bind.annotation.XmlElement;
+ * import javax.xml.bind.annotation.XmlRootElement;
+ * @XmlRootElement
+ * public final class Page {
+ *   @XmlElement
+ *   public String version() {
+ *     return Manifests.read("Foo-Version");
+ *   }
+ *   @XmlElement
+ *   public Date date() {
+ *     return new SimpleDateFormat("yyyy.MM.dd", Locale.ENGLISH).parse(
+ *       Manifests.read("Foo-Date");
+ *     );
+ *   }
+ * }
+ * + *

If you want to add more manifests to the collection, use + * its static instance: + * + *

Manifests.DEFAULT.append(new FilesMfs(new File("MANIFEST.MF")));
+ * + *

You can also modify the map directly: + * + *

Manifests.DEFAULT.put("Hello", "world");
+ * + *

The only dependency you need (check the latest version at + * jcabi-manifests): + * + *

 <dependency>
+ *  <groupId>com.jcabi</groupId>
+ *  <artifactId>jcabi-manifests</artifactId>
+ * </dependency>
+ * + * @author Yegor Bugayenko (yegor@tpc2.com) + * @version $Id$ + * @since 0.7 + * @see JAR Manifest + * @see Maven Archiver + * @see manifests.jcabi.com + * @see How to Read MANIFEST.MF Files + */ +@SuppressWarnings("PMD.TooManyMethods") +public final class Manifests implements MfMap { + + /** + * Default singleton. + */ + public static final MfMap DEFAULT = new Manifests(); + + /** + * Attributes retrieved. + */ + private final transient Map attributes; + + static { + try { + Manifests.DEFAULT.append(new ClasspathMfs()); + } catch (final IOException ex) { + // FIX-UP: Log exception correctly + Logger.error( + Manifests.class, + "#load(): '%s' failed %[exception]s", ex.getMessage(), ex + ); + } + } + + /** + * Public ctor. + * @since 1.0 + */ + public Manifests() { + this(new HashMap(0)); + } + + /** + * Public ctor. + * @param attrs Attributes to encapsulate + * @since 1.0 + */ + public Manifests(final Map attrs) { + super(); + this.attributes = new ConcurrentHashMap(attrs); + } + + @Override + public int size() { + return this.attributes.size(); + } + + @Override + public boolean isEmpty() { + return this.attributes.isEmpty(); + } + + @Override + public boolean containsKey(final Object key) { + return this.attributes.containsKey(key); + } + + @Override + public boolean containsValue(final Object value) { + return this.attributes.containsValue(value); + } + + @Override + public String get(final Object key) { + return this.attributes.get(key); + } + + @Override + public String put(final String key, final String value) { + return this.attributes.put(key, value); + } + + @Override + public String remove(final Object key) { + return this.attributes.remove(key); + } + + @Override + public void putAll(final Map attrs) { + this.attributes.putAll(attrs); + } + + @Override + public void clear() { + this.attributes.clear(); + } + + @Override + public Set keySet() { + return this.attributes.keySet(); + } + + @Override + public Collection values() { + return this.attributes.values(); + } + + @Override + public Set> entrySet() { + return this.attributes.entrySet(); + } + + @Override + public MfMap append(final Mfs streams) throws IOException { + final long start = System.currentTimeMillis(); + final Collection list = streams.fetch(); + int saved = 0; + int ignored = 0; + for (final InputStream stream : list) { + for (final Map.Entry attr + : Manifests.load(stream).entrySet()) { + if (this.attributes.containsKey(attr.getKey())) { + ++ignored; + } else { + this.attributes.put(attr.getKey(), attr.getValue()); + ++saved; + } + } + } + Logger.info( + this, + // @checkstyle LineLength (1 line) + "%d attributes loaded from %d stream(s) in %[ms]s, %d saved, %d ignored: %[list]s", + this.attributes.size(), list.size(), + System.currentTimeMillis() - start, + saved, ignored, + new TreeSet(this.attributes.keySet()) + ); + return this; + } + + /** + * Read one attribute available in one of {@code MANIFEST.MF} files. + * + *

If such a attribute doesn't exist {@link IllegalArgumentException} + * will be thrown. If you're not sure whether the attribute is present or + * not use {@link #exists(String)} beforehand. + * + *

The method is thread-safe. + * + * @param name Name of the attribute + * @return The value of the attribute retrieved + */ + public static String read(final String name) { + if (name == null) { + throw new IllegalArgumentException("attribute can't be NULL"); + } + if (name.isEmpty()) { + throw new IllegalArgumentException("attribute can't be empty"); + } + if (!Manifests.exists(name)) { + throw new IllegalArgumentException( + Logger.format( + // @checkstyle LineLength (1 line) + "Attribute '%s' not found in MANIFEST.MF file(s) among %d other attribute(s): %[list]s", + name, + Manifests.DEFAULT.size(), + new TreeSet(Manifests.DEFAULT.keySet()) + ) + ); + } + return Manifests.DEFAULT.get(name); + } + + /** + * Check whether attribute exists in any of {@code MANIFEST.MF} files. + * + *

Use this method before {@link #read(String)} to check whether an + * attribute exists, in order to avoid a runtime exception. + * + *

The method is thread-safe. + * + * @param name Name of the attribute to check + * @return Returns {@code TRUE} if it exists, {@code FALSE} otherwise + */ + public static boolean exists(final String name) { + if (name == null) { + throw new IllegalArgumentException("attribute name can't be NULL"); + } + if (name.isEmpty()) { + throw new IllegalArgumentException("attribute name can't be empty"); + } + return Manifests.DEFAULT.containsKey(name); + } + + /** + * Load attributes from input stream. + * + *

Inside the method we catch {@code RuntimeException} (which may look + * suspicious) in order to protect our execution flow from expected (!) + * exceptions from {@link Manifest#getMainAttributes()}. For some reason, + * this JDK method doesn't throw checked exceptions if {@code MANIFEST.MF} + * file format is broken. Instead, it throws a runtime exception (an + * unchecked one), which we should catch in such an inconvenient way. + * + * @param stream Stream to load from + * @return The attributes loaded + * @throws IOException If some problem happens + * @since 0.8 + */ + @SuppressWarnings("PMD.AvoidCatchingGenericException") + private static Map load(final InputStream stream) + throws IOException { + final ConcurrentMap props = + new ConcurrentHashMap(0); + try { + final Manifest manifest = new Manifest(stream); + final Attributes attrs = manifest.getMainAttributes(); + for (final Object key : attrs.keySet()) { + final String value = attrs.getValue( + Attributes.Name.class.cast(key) + ); + props.put(key.toString(), value); + } + /* + * FIXUP: Logging statement below is harmful and may result in Inputstreams returned + * by `feed()` method to be closed. + * This will result in the following exception: + * [ERROR] 10:55:15+0000 [main] manifest.Manifests. - #load(): 'Stream closed' failed java.io.IOException: Stream closed + at java.util.zip.InflaterInputStream.ensureOpen(Unknown Source) + at java.util.zip.InflaterInputStream.read(Unknown Source) + at java.io.FilterInputStream.read(Unknown Source) + at java.util.jar.Manifest$FastInputStream.fill(Unknown Source) + at java.util.jar.Manifest$FastInputStream.readLine(Unknown Source) + at java.util.jar.Manifest$FastInputStream.readLine(Unknown Source) + at java.util.jar.Attributes.read(Unknown Source) + at java.util.jar.Manifest.read(Unknown Source) + at java.util.jar.Manifest.(Unknown Source) + at java.util.jar.Manifest.(Unknown Source) + ... + + * Initial investigation shows that this might be related to the order of manifest + * entries discovered in the classpath. + * Also discovered some related changes in the original library: https://github.com/jcabi/jcabi-manifests/pull/43 + * But those were never officially released as a new version. + */ + /* Logger.debug( + Manifests.class, + "%d attribute(s) loaded %[list]s", + props.size(), new TreeSet(props.keySet()) + );*/ + // @checkstyle IllegalCatch (1 line) + } catch (final RuntimeException ex) { + Logger.error(Manifests.class, "#load(): failed %[exception]s", ex); + } finally { + stream.close(); + } + return props; + } + +} diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/CordaVersion.kt b/common/logging/src/main/kotlin/net/corda/common/logging/CordaVersion.kt index 083f0a078a..e1b46c154b 100644 --- a/common/logging/src/main/kotlin/net/corda/common/logging/CordaVersion.kt +++ b/common/logging/src/main/kotlin/net/corda/common/logging/CordaVersion.kt @@ -1,6 +1,6 @@ package net.corda.common.logging -import com.jcabi.manifests.Manifests +import net.corda.common.logging.manifest.Manifests class CordaVersion { companion object { @@ -24,4 +24,4 @@ class CordaVersion { arrayOf("No version data is available in the MANIFEST file.") } } -} \ No newline at end of file +}