001/* 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2026, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v2.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014 015package ch.qos.logback.core.util; 016 017import ch.qos.logback.core.Context; 018import ch.qos.logback.core.status.InfoStatus; 019import ch.qos.logback.core.status.WarnStatus; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.lang.module.ModuleDescriptor; 024import java.util.Optional; 025import java.util.Properties; 026 027import static ch.qos.logback.core.CoreConstants.NA; 028 029// depender depends on dependency 030 031// dependency synonym dependee (only use dependency) 032// depender synonym dependent (only use depender) 033 034/** 035 * Utility class for handling and validating version information of various artifacts. 036 * 037 * <p>It is used by logback-classic, logback-access-common, logback-access-jetty11, logback-access-tomcat, etc. 038 * to alert users about version discrepancies between depender and dependency artifacts. 039 * </p> 040 * 041 * @since 1.5.25 042 */ 043public class VersionUtil { 044 045 /** 046 * Retrieves the version of an artifact, such as logback-core.jar, logback-access-common.jar etc. 047 * 048 * <p>The aClass parameter is assumed to be part of the artifact. 049 * </p> 050 * 051 * <p>The method first attempts to get the version from the module information. If the module version 052 * is not available, it falls back to retrieving the implementation version from the package. 053 * </p> 054 * 055 * @param aClass the class from which to retrieve the version information 056 * @return the version of the artifact where aClass is found, or null if the version cannot be determined 057 * @deprecated 058 */ 059 static public String getVersionOfArtifact(Class<?> aClass) { 060 String moduleVersion = getVersionOfClassByModule(aClass); 061 if (moduleVersion != null) 062 return moduleVersion; 063 064 Package pkg = aClass.getPackage(); 065 if (pkg == null) { 066 return null; 067 } 068 return pkg.getImplementationVersion(); 069 } 070 071 static public String nonNull(String input) { 072 if (input == null) { 073 return NA; 074 } else { 075 return input; 076 } 077 } 078 079 /** 080 * Retrieves the version of an artifact from the artifact's module metadata. 081 * 082 * <p>If the module or its descriptor does not provide a version, the method returns null. 083 * </p> 084 * 085 * @param aClass a class from which to retrieve the version information 086 * @return the version of class' module as a string, or null if the version cannot be determined 087 */ 088 static private String getVersionOfClassByModule(Class<?> aClass) { 089 Module module = aClass.getModule(); 090 if (module == null) 091 return null; 092 093 ModuleDescriptor md = module.getDescriptor(); 094 if (md == null) 095 return null; 096 Optional<String> opt = md.rawVersion(); 097 return opt.orElse(null); 098 } 099 100 /** 101 * Retrieves the version of a module using a properties file associated with the module. 102 * 103 * <p>Unfortunately, this code cannot be called by other modules. It needs to be duplicated.</p> 104 * 105 * <p>The method looks for a properties file with a name derived from the <code>moduleName</code> parameter, 106 * in the same location, e.g. package, as the <code>aClass</code> parameter. It attempts to load the properties file 107 * and fetch the version information using a specific key. 108 * </p> 109 * 110 * <p>The properties file is expected to be in the same package as the class provided, and named 111 * <code>moduleName-version.properties</code>. The properties file should contain a single key-value pair, 112 * where the key is <code>moduleName-version</code>, and the value is the module version. 113 * 114 * @param aClass the class used to locate the resource file, the properties file is expected to be in the same package 115 * @param moduleName the name of the module, which is used to construct the properties file name and the key 116 * @return the version of the module as a string, or null if the version cannot be determined 117 * @since 1.5.26 118 * @deprecated (this code cannot be shared and is useless here) 119 */ 120 static public String getArtifactVersionBySelfDeclaredProperties(Class<?> aClass, String moduleName) { 121 Properties props = new Properties(); 122 // example propertiesFileName: logback-core-version.properties 123 // 124 String propertiesFileName = moduleName + "-version.properties"; 125 String propertyKey = moduleName + "-version"; 126 try (InputStream is = aClass.getResourceAsStream(propertiesFileName)) { 127 if (is != null) { 128 props.load(is); 129 return props.getProperty(propertyKey); 130 } else { 131 return null; 132 } 133 } catch (IOException e) { 134 return null; 135 } 136 } 137 138 139 140 141 static String getExpectedVersionOfDependencyByProperties(Class<?> dependerClass, String propertiesFileName, String dependencyNameAsKey) { 142 Properties props = new Properties(); 143 // propertiesFileName : logback-access-common-dependencies.properties 144 try (InputStream is = dependerClass.getClassLoader() 145 .getResourceAsStream(propertiesFileName)) { 146 if (is != null) { 147 props.load(is); 148 return props.getProperty(dependencyNameAsKey); 149 } else { 150 return null; 151 } 152 } catch (IOException e) { 153 return null; 154 } 155 } 156 157 158 static public void checkForVersionEquality(Context context, Class<?> dependerClass, Class<?> dependencyClass, String dependerName, String dependencyName) { 159 // the depender depends on the dependency 160 String dependerVersion = nonNull(getVersionOfArtifact(dependerClass)); 161 String dependencyVersion = nonNull(getVersionOfArtifact(dependencyClass)); 162 163 checkForVersionEquality(context, dependerVersion, dependencyVersion, dependerName, dependencyName); 164 } 165 166 static public void checkForVersionEquality(Context context, Class<?> dependerClass, String dependencyVersion, String dependerName, String dependencyName) { 167 String dependerVersion = nonNull(getVersionOfArtifact(dependerClass)); 168 checkForVersionEquality(context, dependerVersion, dependencyVersion, dependerName, dependencyName); 169 } 170 171 172 /** 173 * Compares the versions of a depender and a dependency to determine if they are equal. 174 * Updates the context's status manager with version information and logs a warning 175 * if the versions differ. 176 * 177 * @since 1.5.26 178 */ 179 static public void checkForVersionEquality(Context context, String dependerVersion, String dependencyVersion, String dependerName, String dependencyName) { 180 // the depender depends on the dependency 181 addFoundVersionStatus(context, dependerName, dependerVersion); 182 183 dependerVersion = nonNull(dependerVersion); 184 185 if (dependerVersion.equals(NA) || !dependerVersion.equals(dependencyVersion)) { 186 addFoundVersionStatus(context, dependencyName, dependencyVersion); 187 String discrepancyMsg = String.format("Versions of %s and %s are different or unknown.", dependencyName, dependerVersion); 188 context.getStatusManager().add(new WarnStatus(discrepancyMsg, context)); 189 } 190 } 191 192 193 private static void addFoundVersionStatus(Context context, String name, String version) { 194 String foundDependent = String.format("Found %s version %s", name, nonNull(version)); 195 context.getStatusManager().add(new InfoStatus(foundDependent, context)); 196 } 197 198 private static String nameToPropertiesFilename(String name) { 199 return name + "-dependencies.properties"; 200 } 201 202 /** 203 * Compares the expected version of a dependency with the actual version found and updates the status context. 204 * If the versions do not match, a warning is added to the context's status manager. 205 * 206 * <p>Note: This method is used be logback-access-jetty11/12 and logback-access-tomcat.</p> 207 * 208 */ 209 static public void compareExpectedAndFoundVersion(Context context, String actualDependencyVersion, Class<?> dependerClass, String dependerVersion, 210 String dependerName, String dependencyName) { 211 212 String expectedDependencyVersion = nonNull(getExpectedVersionOfDependencyByProperties(dependerClass, nameToPropertiesFilename(dependerName), dependencyName)); 213 214 addFoundVersionStatus(context, dependencyName, actualDependencyVersion); 215 addFoundVersionStatus(context, dependerName, dependerVersion); 216 217 if (!expectedDependencyVersion.equals(actualDependencyVersion)) { 218 String discrepancyMsg = String.format("Expected version of %s is %s but found %s", dependencyName, expectedDependencyVersion, actualDependencyVersion); 219 context.getStatusManager().add(new WarnStatus(discrepancyMsg, context)); 220 } 221 } 222}